В тестировании React-приложений часто возникает необходимость
проверять поведение компонентов, зависящее от времени: таймеры,
задержки, анимации, периодические обновления данных. Для таких случаев
используется функциональность Jest под названием Fake
Timers. Она позволяет полностью контролировать время внутри
теста, ускорять ожидания и предсказывать поведение кода, использующего
setTimeout, setInterval или функции типа
Date.now().
Для включения Fake Timers используется функция:
jest.useFakeTimers();
По умолчанию она заменяет стандартные таймеры JavaScript на
управляемые версии Jest. После вызова этой функции все вызовы
setTimeout, setInterval и
Date.now() подчиняются симулированному времени.
Можно выбрать тип таймеров:
jest.useFakeTimers("modern"); // современный режим, поддерживает Date
jest.useFakeTimers("legacy"); // устаревший режим, аналог старых версий Jest
Современный режим рекомендуется для большинства случаев, так как он поддерживает мок даты и асинхронные таймеры более корректно.
Fake Timers предоставляют ряд методов для контроля времени внутри теста.
jest.advanceTimersByTime(ms)Продвигает симулированное время на указанное количество миллисекунд. Все таймеры, срок которых истек в этом интервале, выполняются синхронно.
test("setTimeout с использованием advanceTimersByTime", () => {
const callback = jest.fn();
setTimeout(callback, 1000);
jest.advanceTimersByTime(500);
expect(callback).not.toHaveBeenCalled();
jest.advanceTimersByTime(500);
expect(callback).toHaveBeenCalledTimes(1);
});
jest.runAllTimers()Выполняет все запланированные таймеры сразу, независимо от их задержки.
test("runAllTimers выполняет все таймеры", () => {
const callback1 = jest.fn();
const callback2 = jest.fn();
setTimeout(callback1, 1000);
setTimeout(callback2, 2000);
jest.runAllTimers();
expect(callback1).toHaveBeenCalled();
expect(callback2).toHaveBeenCalled();
});
jest.runOnlyPendingTimers()Выполняет только те таймеры, которые уже были запланированы, но не добавляет новые таймеры, созданные внутри текущих колбэков.
test("runOnlyPendingTimers не выполняет новые таймеры", () => {
const callback = jest.fn();
setTimeout(() => {
callback();
setTimeout(callback, 1000); // этот таймер не выполнится
}, 500);
jest.runOnlyPendingTimers();
expect(callback).toHaveBeenCalledTimes(1);
});
jest.advanceTimersToNextTimer()Пошагово продвигает время до следующего таймера. Полезно для проверки последовательного выполнения нескольких таймеров с разными задержками.
test("advanceTimersToNextTimer выполняет следующий таймер", () => {
const callback = jest.fn();
setTimeout(() => callback("first"), 100);
setTimeout(() => callback("second"), 200);
jest.advanceTimersToNextTimer();
expect(callback).toHaveBeenCalledWith("first");
jest.advanceTimersToNextTimer();
expect(callback).toHaveBeenCalledWith("second");
});
Fake Timers в современном режиме ("modern") поддерживают
работу с async/await. Для корректного выполнения
асинхронного кода необходимо использовать:
await jest.advanceTimersByTimeAsync(ms);
Это особенно актуально при тестировании React-компонентов с
useEffect или асинхронными обновлениями состояния.
test("async таймер с useEffect", async () => {
const callback = jest.fn();
useEffect(() => {
const timer = setTimeout(() => {
callback();
}, 1000);
return () => clearTimeout(timer);
}, []);
await jest.advanceTimersByTimeAsync(1000);
expect(callback).toHaveBeenCalled();
});
Современные Fake Timers позволяют управлять результатами вызовов
Date.now() и new Date(). Это полезно для
компонентов, зависящих от текущего времени.
jest.setSystemTime(new Date("2026-01-01T12:00:00Z"));
console.log(Date.now()); // 1672574400000
console.log(new Date().toISOString()); // "2026-01-01T12:00:00.000Z"
Можно комбинировать setSystemTime с
advanceTimersByTime, чтобы эмулировать течение времени:
jest.setSystemTime(new Date("2026-01-01T12:00:00Z"));
jest.advanceTimersByTime(3600 * 1000); // +1 час
console.log(new Date().toISOString()); // "2026-01-01T13:00:00.000Z"
После теста рекомендуется сбрасывать состояние таймеров, чтобы не влиять на последующие тесты:
jest.useRealTimers();
Это возвращает оригинальные таймеры JavaScript и удаляет мок даты.
При тестировании компонентов с таймерами часто применяются следующие подходы:
useEffect:function TimerComponent() {
const [count, setCount] = useState(0);
useEffect(() => {
const timer = setTimeout(() => setCount(1), 1000);
return () => clearTimeout(timer);
}, []);
return <div>{count}</div>;
}
test("TimerComponent обновляет состояние через 1 секунду", () => {
render(<TimerComponent />);
expect(screen.getByText("0")).toBeInTheDocument();
jest.advanceTimersByTime(1000);
expect(screen.getByText("1")).toBeInTheDocument();
});
setInterval:function IntervalComponent() {
const [count, setCount] = useState(0);
useEffect(() => {
const interval = setInterval(() => setCount(c => c + 1), 1000);
return () => clearInterval(interval);
}, []);
return <div>{count}</div>;
}
test("IntervalComponent увеличивает count каждую секунду", () => {
render(<IntervalComponent />);
jest.advanceTimersByTime(3000);
expect(screen.getByText("3")).toBeInTheDocument();
});
test("обновление после fetch с задержкой", async () => {
const fetchMock = jest.fn().mockResolvedValue({ data: "ok" });
render(<AsyncComponent fetchFn={fetchMock} />);
await jest.advanceTimersByTimeAsync(1000);
expect(fetchMock).toHaveBeenCalled();
expect(screen.getByText("ok")).toBeInTheDocument();
});
"modern" режим, чтобы не ломать работу с
Date.jest.useRealTimers() или в afterEach.screen и act() для корректного обновления
DOM.advanceTimersByTime для пошаговой проверки
поведения и runAllTimers для одновременного выполнения всех
таймеров.Fake Timers делают тесты быстрыми, предсказуемыми и надежными, устраняя зависимость от реального времени и сетевых задержек.