ResizeObserver мокирование

В современном фронтенд-разработке компоненты часто зависят от размеров элементов DOM для корректного рендеринга и логики. Например, адаптивные таблицы, карусели, динамические сетки и компоненты с анимациями могут использовать ResizeObserver для отслеживания изменений размеров. В среде тестирования, такой как Jest с React Testing Library, ResizeObserver отсутствует в стандартной среде Node.js, что приводит к ошибкам вроде:

ReferenceError: ResizeObserver is not defined

Для корректного тестирования требуется его мокирование.


Подходы к мокированию

1. Полное замещение

Можно создать глобальный мок в файле конфигурации тестов (например, setupTests.js), чтобы все тесты использовали поддельную реализацию ResizeObserver:

class ResizeObserverMock {
  constructor(callback) {
    this.callback = callback;
  }

  observe(target) {
    this.callback([{ target }]); // сразу вызываем callback для имитации изменения
  }

  unobserve() {}

  disconnect() {}
}

global.ResizeObserver = ResizeObserverMock;

Ключевые моменты:

  • Метод observe должен принимать DOM-элемент и вызывать callback, чтобы компоненты, зависящие от изменения размеров, корректно инициализировались.
  • Методы unobserve и disconnect можно оставить пустыми, если в тестах не проверяется их поведение.
  • Этот мок работает для большинства сценариев, где ResizeObserver используется только для наблюдения за размерами.

2. Использование Jest Spy

Если необходимо контролировать вызовы методов ResizeObserver, можно использовать jest.fn() для слежки за вызовами:

const observeMock = jest.fn();
const unobserveMock = jest.fn();
const disconnectMock = jest.fn();

global.ResizeObserver = jest.fn().mockImplementation((callback) => ({
  observe: observeMock,
  unobserve: unobserveMock,
  disconnect: disconnectMock,
}));

Особенности такого подхода:

  • Позволяет проверять, вызывались ли методы observe, unobserve или disconnect с правильными аргументами:

    expect(observeMock).toHaveBeenCalledWith(expect.any(HTMLElement));
  • Подходит для unit-тестов, где важно убедиться в правильной интеграции компонента с API ResizeObserver.


3. Динамическое изменение размеров

Для более сложных сценариев, когда требуется имитировать изменение размеров элемента после монтирования, можно вызывать callback вручную:

let resizeCallback;

global.ResizeObserver = class {
  constructor(callback) {
    resizeCallback = callback;
  }

  observe(target) {
    // начальная симуляция размера
    resizeCallback([{ target, contentRect: { width: 100, height: 100 } }]);
  }

  unobserve() {}

  disconnect() {}
};

// В тесте можно изменять размеры
resizeCallback([{ target: divElement, contentRect: { width: 200, height: 200 } }]);

Преимущества:

  • Позволяет тестировать адаптивные компоненты и реакции на изменение размеров.
  • Можно имитировать серию событий, проверяя, корректно ли компонент реагирует на обновления.

Интеграция с React Testing Library

При мокировании ResizeObserver в сочетании с RTL важно учитывать асинхронность обновлений компонента:

import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import ResponsiveComponent from './ResponsiveComponent';

test('отслеживание изменения размера', async () => {
  const { container } = render(<ResponsiveComponent />);
  
  const box = container.querySelector('.resize-box');

  // имитация изменения размера
  resizeCallback([{ target: box, contentRect: { width: 300, height: 300 } }]);
  
  // проверка реакции компонента
  expect(screen.getByText('Ширина: 300')).toBeInTheDocument();
});

Важно:

  • ResizeObserver вызывается синхронно в моках, но компонент может обновлять состояние асинхронно через setState. Поэтому при необходимости использовать await waitFor(...) или findBy... для ожидания ререндеров.
  • Мок можно поместить в setupTests.js, чтобы не дублировать его в каждом тесте.

Практические рекомендации

  • Универсальность: Для большинства тестов достаточно простого класса с пустыми методами unobserve и disconnect.
  • Контроль вызовов: Для unit-тестов с проверкой интеграции лучше использовать jest.fn().
  • Сценарии изменения размеров: Для тестирования адаптивного поведения компонента необходимо хранить ссылку на callback и вызывать его с разными значениями contentRect.
  • Изоляция тестов: При изменении глобального ResizeObserver убедиться, что мок сбрасывается между тестами, чтобы избежать побочных эффектов.

Мокирование ResizeObserver позволяет безопасно тестировать компоненты, зависящие от размеров DOM, и интегрировать их с React Testing Library без ошибок среды Node.js. Правильная организация мока обеспечивает как простое прохождение тестов, так и возможность проверки сложной логики адаптивного поведения.