useEffect является одним из ключевых хуков в React,
обеспечивая выполнение побочных эффектов в функциональных компонентах.
Тестирование компонентов с useEffect требует понимания как
самого жизненного цикла компонента, так и асинхронного поведения,
которое может быть инициировано внутри эффекта. В React Testing Library
акцент делается на тестировании поведения, а не внутренних реализаций,
поэтому подходы к тестированию useEffect ориентированы на
наблюдение за результатами его работы.
Часто useEffect используется для обновления состояния
после монтирования компонента или изменения пропсов:
useEffect(() => {
setData(fetchData());
}, [dependency]);
В тестах необходимо убедиться, что состояние компонента меняется корректно, что можно проверить через визуальные изменения или DOM-узлы, а не через прямое обращение к состоянию.
Пример:
import { render, screen, waitFor } from '@testing-library/react';
import MyComponent from './MyComponent';
import { fetchData } from './api';
jest.mock('./api');
test('отрисовывает данные после эффекта', async () => {
fetchData.mockResolvedValue(['Item 1', 'Item 2']);
render(<MyComponent />);
await waitFor(() => {
expect(screen.getByText('Item 1')).toBeInTheDocument();
expect(screen.getByText('Item 2')).toBeInTheDocument();
});
});
Ключевые моменты:
waitFor для ожидания асинхронного
эффекта.Эффекты могут инициировать сетевые запросы или взаимодействовать с другими сервисами. Тестирование таких эффектов требует изоляции и контроля внешних зависимостей.
Подходы:
jest.mock или специализированных библиотек вроде
msw.useEffect применяются методы
jest.useFakeTimers и jest.runAllTimers.Пример таймера:
useEffect(() => {
const interval = setInterval(() => {
setCount(prev => prev + 1);
}, 1000);
return () => clearInterval(interval);
}, []);
Тест:
import { render, screen } from '@testing-library/react';
import Counter from './Counter';
jest.useFakeTimers();
test('увеличивает счетчик каждые 1000 мс', () => {
render(<Counter />);
expect(screen.getByText('0')).toBeInTheDocument();
jest.advanceTimersByTime(1000);
expect(screen.getByText('1')).toBeInTheDocument();
jest.advanceTimersByTime(2000);
expect(screen.getByText('3')).toBeInTheDocument();
});
Особенности:
return в
useEffect проверяется косвенно: отсутствие ошибок при
размонтировании.cleanup)Многие эффекты возвращают функцию очистки. Важно убедиться, что эта функция выполняется корректно при размонтировании компонента.
Пример:
useEffect(() => {
const handleResize = () => setWidth(window.innerWidth);
window.addEventListener('resize', handleResize);
return () => window.removeEventListener('resize', handleResize);
}, []);
Тест:
import { render, unmountComponentAtNode } from '@testing-library/react';
import ResizeComponent from './ResizeComponent';
test('очищает обработчик при размонтировании', () => {
const container = document.createElement('div');
document.body.appendChild(container);
render(<ResizeComponent />, { container });
const spy = jest.spyOn(window, 'removeEventListener');
unmountComponentAtNode(container);
expect(spy).toHaveBeenCalledWith('resize', expect.any(Function));
});
Особенности:
jest.spyOn).actReact Testing Library автоматически оборачивает большинство действий
в act, но при асинхронных эффектах требуется использовать
waitFor или findBy методы, чтобы дождаться
изменений:
const result = await screen.findByText('Данные загружены');
expect(result).toBeInTheDocument();
Использование await гарантирует, что тест дождется
завершения асинхронного эффекта перед проверкой результата.
useEffectПроверка правильного срабатывания эффекта при изменении зависимостей:
useEffect(() => {
console.log('Зависимость изменилась:', propValue);
}, [propValue]);
Тест:
import { render, screen } from '@testing-library/react';
import PropComponent from './PropComponent';
test('эффект срабатывает при изменении пропса', () => {
const { rerender } = render(<PropComponent propValue="A" />);
// Проверка первоначального эффекта
expect(screen.getByText('A')).toBeInTheDocument();
rerender(<PropComponent propValue="B" />);
// Эффект должен сработать снова
expect(screen.getByText('B')).toBeInTheDocument();
});
useEffectsetState.fetch, axios, таймеры).waitFor или
findBy* методы.unmount и jest.spyOn.Тестирование useEffect становится надежным, если
комбинировать наблюдение за DOM, мокирование внешних зависимостей и
корректное использование асинхронных утилит библиотеки.