В современном фронтенд-разработке часто используются API браузера,
которые взаимодействуют с реальной средой, например,
IntersectionObserver. Этот API позволяет отслеживать
видимость элементов на экране и широко применяется для ленивой подгрузки
контента, анимаций при скролле и infinite scroll. В тестах React
компонентов прямое использование IntersectionObserver
приводит к проблемам: Jest и среда JSDOM не реализуют полноценный
браузерный API для наблюдения за пересечениями элементов, поэтому тесты
без мокирования будут падать.
1. Создание глобального мока
Для стабильного тестирования необходимо заменить стандартный
IntersectionObserver на мок, который имитирует поведение
API без реального взаимодействия с DOM.
Пример базового мока:
beforeAll(() => {
class IntersectionObserverMock {
constructor(callback, options) {
this.callback = callback;
this.options = options;
}
observe = jest.fn();
unobserve = jest.fn();
disconnect = jest.fn();
takeRecords = jest.fn();
}
Object.defineProperty(window, 'IntersectionObserver', {
writable: true,
configurable: true,
value: IntersectionObserverMock
});
});
Ключевые моменты:
observe, unobserve,
disconnect заменены на jest.fn(), чтобы можно
было проверять их вызовы.callback и options,
имитируя настоящий API.2. Контроль срабатывания коллбэка
Для проверки логики компонентов важно имитировать срабатывание события пересечения. Это достигается вызовом коллбэка мок-объекта:
function triggerIntersection(entries) {
window.IntersectionObserver.mock.instances.forEach(instance => {
instance.callback(entries);
});
}
Пример использования в тесте:
import { render, screen } from '@testing-library/react';
import LazyImage from './LazyImage';
test('загружает изображение при пересечении', () => {
render(<LazyImage src="image.jpg" alt="demo" />);
const img = screen.getByAltText('demo');
expect(img.src).toContain('placeholder');
triggerIntersection([{ isIntersecting: true, target: img }]);
expect(img.src).toContain('image.jpg');
});
Ключевой момент: isIntersecting
определяет видимость элемента. Можно передавать массив с разными
элементами для тестирования нескольких наблюдаемых объектов.
1. Настройка разных состояний пересечения
Для сложных компонентов с несколькими точками срабатывания можно использовать кастомные фабрики моков:
function createIntersectionObserverMock(callback) {
return {
observe: jest.fn(),
unobserve: jest.fn(),
disconnect: jest.fn(),
callback
};
}
beforeEach(() => {
const instances = [];
window.IntersectionObserver = jest.fn((callback) => {
const instance = createIntersectionObserverMock(callback);
instances.push(instance);
return instance;
});
window.IntersectionObserver.instances = instances;
});
Позволяет в тестах получать конкретные экземпляры и манипулировать их состоянием:
const instance = window.IntersectionObserver.instances[0];
instance.callback([{ isIntersecting: true, target: element }]);
2. Использование асинхронного поведения
Для компонентов с асинхронной загрузкой часто нужно дождаться эффекта пересечения:
import { waitFor } from '@testing-library/react';
await waitFor(() => {
expect(img.src).toContain('image.jpg');
});
Это гарантирует, что компонент успел обработать событие пересечения и обновить состояние.
IntersectionObserver is not defined.useEffect без
моков – асинхронные эффекты могут не выполниться, если коллбэк
не вызван вручную.disconnect – для
компонентов, которые снимают наблюдение, важно убедиться, что ресурсы
очищаются.Пример проверки вызова disconnect:
import { unmount } from '@testing-library/react';
const instance = window.IntersectionObserver.instances[0];
unmount();
expect(instance.disconnect).toHaveBeenCalled();
IntersectionObserver
глобально в setupTests.js или в
beforeAll.triggerIntersection).observe и disconnect.Эта методика позволяет создавать надежные и предсказуемые тесты React компонентов, использующих IntersectionObserver, обеспечивая контроль над всеми состояниями видимости элементов и предотвращая зависимость от реального DOM браузера.