jest.runAllTimers, jest.advanceTimersByTime

Тестирование компонентов и функций, которые используют таймеры (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 Testing Library

При тестировании 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).

Практические советы

  1. Всегда включать jest.useFakeTimers() перед тестами с таймерами.
  2. Использовать advanceTimersByTime для пошагового контроля времени и проверки промежуточных состояний.
  3. Использовать runAllTimers для немедленного выполнения всех таймеров, когда последовательность не критична.
  4. Комбинировать с асинхронными ожиданиями, чтобы корректно работать с промисами, запущенными внутри таймеров.
  5. Возвращать реальные таймеры после теста, чтобы не нарушать работу других тестов:
afterEach(() => {
  jest.useRealTimers();
});

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