Тестирование компонентов, которые используют таймеры, асинхронные
задержки или периодические обновления через setInterval,
требует особого подхода. В противном случае тесты могут становиться
ненадёжными, долго выполняться или вовсе зависать. React Testing Library
(RTL) в связке с Jest предоставляет инструменты для контроля времени,
позволяя эмулировать поведение таймеров и ускорять выполнение
тестов.
Jest позволяет переключиться на использование фиктивных
таймеров через jest.useFakeTimers(). Это даёт
возможность управлять временем в тесте вручную:
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import TimerComponent from './TimerComponent';
jest.useFakeTimers();
test('отображает счетчик через 1 секунду', () => {
render(<TimerComponent />);
// Изначально счетчик равен 0
expect(screen.getByText('0')).toBeInTheDocument();
// Продвигаем таймер на 1 секунду
jest.advanceTimersByTime(1000);
expect(screen.getByText('1')).toBeInTheDocument();
});
Ключевые моменты:
jest.useFakeTimers() заменяет реальные таймеры
Jest-емуляцией.jest.advanceTimersByTime(ms) позволяет
пропустить определённое количество миллисекунд, не
ожидая реального времени.jest.useRealTimers(), чтобы избежать побочных эффектов в
других тестах.setIntervalКомпоненты с периодическим обновлением, например счетчики или ленты
новостей, используют setInterval. Тестирование таких
сценариев требует последовательного продвижения времени.
import { render, screen } from '@testing-library/react';
import IntervalCounter from './IntervalCounter';
jest.useFakeTimers();
test('счетчик увеличивается каждые 500ms', () => {
render(<IntervalCounter />);
expect(screen.getByText('0')).toBeInTheDocument();
// Пропускаем 500ms, счетчик должен увеличить значение
jest.advanceTimersByTime(500);
expect(screen.getByText('1')).toBeInTheDocument();
// Ещё 500ms — счетчик увеличивается ещё раз
jest.advanceTimersByTime(500);
expect(screen.getByText('2')).toBeInTheDocument();
});
Советы при работе с setInterval:
jest.runOnlyPendingTimers() — выполняет все
запланированные таймеры без продвижения времени. Полезно, когда
необходимо мгновенно выполнить все текущие отложенные функции.jest.clearAllTimers() — очищает все таймеры, что
особенно важно для компонентов с повторяющимися интервалами, чтобы
избежать утечек памяти между тестами.act()Когда компоненты обновляются через таймеры или интервалы, важно
использовать act(), чтобы React корректно обработал
изменения состояния:
import { render, screen, act } from '@testing-library/react';
import IntervalCounter from './IntervalCounter';
jest.useFakeTimers();
test('правильное обновление состояния с интервалом', () => {
render(<IntervalCounter />);
act(() => {
jest.advanceTimersByTime(1500); // три тика по 500ms
});
expect(screen.getByText('3')).toBeInTheDocument();
});
act() гарантирует, что все побочные эффекты и
рендеры завершены до проверки элементов.act().Компоненты часто используют комбинацию setTimeout и
сетевых запросов:
import { render, screen, waitFor } from '@testing-library/react';
import AsyncMessage from './AsyncMessage';
jest.useFakeTimers();
test('отображает сообщение после задержки', async () => {
render(<AsyncMessage />);
jest.advanceTimersByTime(2000); // задержка 2 секунды
await waitFor(() => expect(screen.getByText('Готово')).toBeInTheDocument());
});
Особенности:
waitFor позволяет ждать изменений в DOM без
фиксированной задержки.Всегда очищать таймеры после теста:
afterEach(() => {
jest.clearAllTimers();
jest.useRealTimers();
});Не использовать реальные setTimeout и
setInterval в тестах, если они влияют на
критическую логику. Фейковые таймеры делают тесты быстрыми и
предсказуемыми.
Комбинировать act() и
jest.advanceTimersByTime для корректного
обновления состояния компонентов.
Избегать длинных интервалов в компонентах в тестах. Лучше использовать короткие значения или эмулировать поведение таймера.
Проверять все побочные эффекты таймеров: обновление состояния, вызовы колбэков, изменения DOM.
Если компонент использует setInterval внутри
useEffect, важно корректно его очищать:
useEffect(() => {
const interval = setInterval(() => setCount(c => c + 1), 1000);
return () => clearInterval(interval); // предотвращение утечек
}, []);
Без очистки таймеров jest.clearAllTimers() может не
справиться с оставшимися активными интервалами, вызывая неожиданные
ошибки или предупреждения React.
Для компонентов с несколькими таймерами можно управлять ими отдельно:
jest.useFakeTimers();
const timer1 = jest.fn();
const timer2 = jest.fn();
setTimeout(timer1, 1000);
setInterval(timer2, 500);
jest.advanceTimersByTime(1000); // вызывается timer1 и 2 раза timer2
expect(timer1).toHaveBeenCalledTimes(1);
expect(timer2).toHaveBeenCalledTimes(2);
Такой подход позволяет проверять точное количество вызовов каждого таймера, что важно для компонентов с параллельными асинхронными эффектами.
Контроль таймеров с помощью Jest в React Testing Library делает тесты
быстрыми, детерминированными и безопасными, позволяя
полностью эмулировать поведение setTimeout и
setInterval без необходимости ожидать реальное время.