Lesson 8 / 25

Fake Timers

Test debounce, polling and timeouts without waiting.

Advance time instead of sleeping

Code using setTimeout, setInterval or Date is slow and flaky to test with real time. Fake timers replace these globals with a controllable clock: jest.useFakeTimers() / vi.useFakeTimers() turn them on, advanceTimersByTime(ms) moves the clock forward and fires due callbacks, runAllTimers() flushes everything (careful with intervals that reschedule forever) and runOnlyPendingTimers() fires only what is currently queued. When timer callbacks themselves await promises, use the async variants such as advanceTimersByTimeAsync. Always switch back with useRealTimers() in afterEach, so later tests are not affected.

Testing a debounce

Vitest shown; Jest uses jest.* with the same method names.

import { afterEach, beforeEach, expect, it, vi } from 'vitest';
import { debounce } from './debounce';

beforeEach(() => {
  vi.useFakeTimers();
});
afterEach(() => {
  vi.useRealTimers();
});

it('calls the function once after the quiet period', () => {
  const save = vi.fn();
  const debounced = debounce(save, 300);

  debounced('a');
  debounced('ab');
  vi.advanceTimersByTime(299);
  expect(save).not.toHaveBeenCalled();

  vi.advanceTimersByTime(1);
  expect(save).toHaveBeenCalledTimes(1);
  expect(save).toHaveBeenCalledWith('ab');
});

A remote control for the clock

Fake timers are a remote with a fast-forward button: instead of waiting five minutes for the cake timer, you skip ahead and check the cake is out of the oven.

Quick check: What does vi.advanceTimersByTime(500) do under fake timers?

  • Sets a 500 ms timeout on the test
  • Sleeps the test for 500 ms of real time
  • Moves the fake clock 500 ms forward and runs any callbacks that became due
  • Skips the next 500 tests
Answer

Moves the fake clock 500 ms forward and runs any callbacks that became due — Time only moves when the test says so.