Тестирование компонентов и функций, которые используют таймеры
(setTimeout, setInterval,
setImmediate), требует особого подхода. В Jest
предусмотрены специальные методы для контроля виртуальных таймеров, что
позволяет ускорять тесты и проверять асинхронное поведение без реальных
задержек. Основные инструменты для этого —
jest.runAllTimers и
jest.advanceTimersByTime.
Перед использованием методов управления таймерами необходимо включить мокирование таймеров:
jest.useFakeTimers();
Это переключает все таймеры на виртуальные, позволяя управлять их выполнением вручную. После теста рекомендуется возвращать таймеры к реальному поведению:
jest.useRealTimers();
jest.runAllTimersМетод jest.runAllTimers() запускает все
ожидающие таймеры синхронно. Это включает все
setTimeout, setInterval и
setImmediate, зарегистрированные до момента вызова.
Пример использования:
function delayedCallback(callback) {
setTimeout(() => {
callback('done');
}, 1000);
}
test('вызов колбэка после таймера', () => {
const callback = jest.fn();
delayedCallback(callback);
// Колбэк ещё не вызван
expect(callback).not.toHaveBeenCalled();
jest.runAllTimers();
// Теперь все таймеры выполнены
expect(callback).toHaveBeenCalledWith('done');
});
Особенности метода:
jest.advanceTimersByTimeМетод jest.advanceTimersByTime(msToRun) позволяет
сдвинуть виртуальное время на указанное количество
миллисекунд, что позволяет пошагово контролировать выполнение
таймеров.
Пример:
function periodicLogger(callback) {
setInterval(() => {
callback('tick');
}, 1000);
}
test('пошаговое выполнение интервала', () => {
const callback = jest.fn();
periodicLogger(callback);
jest.advanceTimersByTime(1000);
expect(callback).toHaveBeenCalledTimes(1);
jest.advanceTimersByTime(2000);
expect(callback).toHaveBeenCalledTimes(3);
});
Ключевые моменты:
| Метод | Что делает | Применение |
|---|---|---|
jest.runAllTimers() |
Выполняет все зарегистрированные таймеры сразу | Когда важен результат выполнения всех таймеров, а не их порядок по времени |
jest.advanceTimersByTime(ms) |
Сдвигает виртуальное время на ms, выполняя таймеры,
срок которых наступил |
Когда важен контроль последовательности и временных промежутков |
Таймеры часто используются вместе с async/await или
промисами. В таких случаях можно комбинировать таймеры с ожиданием
промисов через await Promise.resolve() или
await jest.runAllTimers():
test('асинхронная функция с таймером', async () => {
const callback = jest.fn();
setTimeout(() => callback('async done'), 500);
jest.advanceTimersByTime(500);
// Таймер выполнен, но промисы ещё могут быть в очереди
await Promise.resolve();
expect(callback).toHaveBeenCalledWith('async done');
});
При тестировании React-компонентов таймеры часто используются для анимаций, debounce или delayed fetch:
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { SearchInput } from './SearchInput';
jest.useFakeTimers();
test('дебаунс при вводе текста', () => {
render(<SearchInput />);
userEvent.type(screen.getByRole('textbox'), 'react');
// До срабатывания debounce колбэк ещё не вызван
expect(screen.queryByText('Searching...')).not.toBeInTheDocument();
jest.advanceTimersByTime(500);
// После debounce
expect(screen.getByText('Searching...')).toBeInTheDocument();
});
Особенности применения:
setTimeout внутри компонента можно контролировать
через Jest.advanceTimersByTime можно имитировать дебаунс
или интервальные обновления.runAllTimers полезен для мгновенного выполнения всех
отложенных операций (например, очистка очереди таймеров после
unmount).jest.useFakeTimers()
перед тестами с таймерами.advanceTimersByTime для пошагового
контроля времени и проверки промежуточных состояний.runAllTimers для немедленного
выполнения всех таймеров, когда последовательность не
критична.afterEach(() => {
jest.useRealTimers();
});
Эффективное управление таймерами позволяет писать тесты, которые проверяют сложное асинхронное поведение без задержек и делает тесты предсказуемыми и быстрыми.