jest.useFakeTimers и управление временем

В тестировании 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"

Очистка Fake Timers

После теста рекомендуется сбрасывать состояние таймеров, чтобы не влиять на последующие тесты:

jest.useRealTimers();

Это возвращает оригинальные таймеры JavaScript и удаляет мок даты.


Использование в React Testing Library

При тестировании компонентов с таймерами часто применяются следующие подходы:

  1. Симуляция задержек в 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();
});
  1. Тестирование периодических обновлений с 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();
});
  1. Комбинация с асинхронными эффектами:
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.
  • Комбинировать Fake Timers с React Testing Library через screen и act() для корректного обновления DOM.
  • Использовать advanceTimersByTime для пошаговой проверки поведения и runAllTimers для одновременного выполнения всех таймеров.

Fake Timers делают тесты быстрыми, предсказуемыми и надежными, устраняя зависимость от реального времени и сетевых задержек.