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

Урок учит тестировать код с 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Чтобы заменить expect
CЧтобы не ждать реальное время и сделать тесты быстрыми и детерминированными
DЧтобы ускорить сеть
2. Что делает jest.advanceTimersByTime(1000)?
AЖдёт 1000 реальных миллисекунд
BУдаляет все таймеры
CЗамедляет таймеры в 1000 раз
DПерематывает виртуальное время на 1000 мс и выполняет наступившие таймеры
3. Почему после теста с фейковыми таймерами вызывают useRealTimers()?
AЧтобы вернуть настоящие таймеры и не сломать другие тесты
BДля скорости установки
CЧтобы удалить моки
DЭто обязательное требование npm