Фейковые таймеры

Урок учит тестировать код с setTimeout/setInterval без реального ожидания времени.

Фейковые таймеры подменяют setTimeout/setInterval, давая тесту управлять «временем» вручную.

Проблема настоящего времени

Код с setTimeout(fn, 5000) нельзя честно тестировать, ожидая 5 секунд: тесты должны быть быстрыми. И от реального времени они становятся недетерминированными. Решение — фейковые таймеры: время «перематывает» сам тест.

Включение фейковых таймеров

jest.useFakeTimers();

test('колбэк вызывается через 1 секунду', () => {
  const cb = jest.fn();

  setTimeout(cb, 1000);

  expect(cb).not.toHaveBeenCalled();   // время ещё не прошло

  jest.advanceTimersByTime(1000);      // перематываем 1 секунду

  expect(cb).toHaveBeenCalledTimes(1); // теперь вызвался
});

Тест выполняется мгновенно, хотя «логически» прошла секунда.

Полезные методы

МетодЧто делает
advanceTimersByTime(ms)перематывает время на ms миллисекунд
runAllTimers()выполняет все ожидающие таймеры
runOnlyPendingTimers()выполняет только текущие (без новых, созданных ими)
useRealTimers()возвращает настоящие таймеры

Идея «виртуального времени» на чистом JS

Смоделируем перемотку времени без реального ожидания:

// мини-планировщик с виртуальными часами
const queue = [];
let now = 0;

function schedule(fn, delay) {
  queue.push({ time: now + delay, fn });
}
function advance(ms) {
  now += ms;
  queue.filter(t => t.time <= now).forEach(t => t.fn());
}

let called = false;
schedule(() => { called = true; }, 1000);

console.log('сразу после schedule, called =', called);
advance(1000); // "перематываем" 1 секунду
console.log('после advance(1000), called =', called);

Вывод:

сразу после schedule, called = false
после advance(1000), called = true

Именно так и работают фейковые таймеры Jest: задачи лежат в очереди с «временем срабатывания», а advanceTimersByTime двигает виртуальные часы и выполняет всё, что подошло.

Не забывайте откатить

После теста с фейковыми таймерами их сбрасывают, чтобы не сломать соседние тесты:

afterEach(() => {
  jest.useRealTimers();
});

Итог

  • jest.useFakeTimers() подменяет таймеры, чтобы не ждать реальное время.
  • advanceTimersByTime(ms) перематывает время и выполняет наступившие колбэки.
  • Тест становится быстрым и детерминированным.
  • После — useRealTimers() для изоляции.
Проверьте себя
1. Зачем нужны фейковые таймеры?
AЧтобы тесты выполнялись медленнее
BЧтобы не ждать реальное время и сделать тесты быстрыми и детерминированными
CЧтобы заменить expect
DЧтобы ускорить сеть
2. Что делает jest.advanceTimersByTime(1000)?
AЖдёт 1000 реальных миллисекунд
BПерематывает виртуальное время на 1000 мс и выполняет наступившие таймеры
CУдаляет все таймеры
DЗамедляет таймеры в 1000 раз
3. Почему после теста с фейковыми таймерами вызывают useRealTimers()?
AДля скорости установки
BЧтобы вернуть настоящие таймеры и не сломать другие тесты
CЧтобы удалить моки
DЭто обязательное требование npm
Поддержать проект