Testland
Browse all skills & agents

jest-fake-timers

Wraps Jest's built-in modern fake-timers (built on @sinonjs/fake-timers since Jest 27): jest.useFakeTimers(), jest.setSystemTime(), jest.advanceTimersByTime(), jest.runAllTimers(), and jest.useRealTimers() for selective restoration. Use when testing JS/TS code in Jest where setTimeout / setInterval / Date / Date.now need deterministic control. Composes dst-transition-reference.

jest-fake-timers

Overview

Per jestjs.io/docs/timer-mocks, Jest 27+ uses modern fake timers built on @sinonjs/fake-timers. The API differs slightly from raw Sinon - Jest exposes jest.advanceTimersByTime instead of clock.tick, and jest.setSystemTime instead of clock.setSystemTime.

When to use

  • Jest tests for code using setTimeout / setInterval / Date.
  • Promise-based async timing tests.
  • Replacing legacy useFakeTimers('legacy') with modern.

Authoring

Enable (per test file)

beforeAll(() => {
  jest.useFakeTimers();
  jest.setSystemTime(new Date('2026-05-20T14:30:00Z'));
});

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

Enable (per test)

test('debounce', () => {
  jest.useFakeTimers();
  jest.setSystemTime(new Date('2026-05-20T14:30:00Z'));

  // ... test body

  jest.useRealTimers();
});

Advance time

test('debounce fires after 300ms', () => {
  let fired = false;
  setTimeout(() => { fired = true; }, 300);

  jest.advanceTimersByTime(299);
  expect(fired).toBe(false);

  jest.advanceTimersByTime(1);
  expect(fired).toBe(true);
});

Promise-based timers

For async code:

test('async debounce', async () => {
  let resolved = false;
  setTimeout(async () => {
    await fetchData();
    resolved = true;
  }, 100);

  await jest.advanceTimersByTimeAsync(100);
  expect(resolved).toBe(true);
});

Per Jest docs, advanceTimersByTimeAsync lets microtasks run between timer ticks.

Run all pending timers

test('chain of timeouts completes', () => {
  let count = 0;
  function recur() {
    if (++count < 5) setTimeout(recur, 100);
  }
  recur();

  jest.runAllTimers();
  expect(count).toBe(5);
});

Run only pending (not recursive)

jest.runOnlyPendingTimers();

Avoids infinite loops for self-scheduling code.

Selective faking

jest.useFakeTimers({
  doNotFake: ['nextTick', 'queueMicrotask'],
  now: new Date('2026-05-20T14:30:00Z').getTime(),
});

DST tests

beforeAll(() => {
  process.env.TZ = 'America/New_York';
  jest.useFakeTimers();
});

test('spring-forward behaviour', () => {
  jest.setSystemTime(new Date('2026-03-08T06:30:00Z'));  // 02:30 EDT - invalid local
  expect(new Date().toString()).toMatch(/03:30/);  // Browser/Node normalises
});

Mix fake-timers with real fetch

If fetch is mocked separately, ensure the mock awaits a faked timer too:

test('debounce + fetch', async () => {
  global.fetch = jest.fn().mockResolvedValue({ json: () => ({ ok: true }) });

  myDebouncedFetch();

  await jest.advanceTimersByTimeAsync(300);
  expect(fetch).toHaveBeenCalled();
});

Running

npx jest

CI integration

jobs:
  jest-tests:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v5
      - uses: actions/setup-node@v4
      - run: npm ci && npx jest

Anti-patterns

Anti-patternWhy it failsFix
jest.useFakeTimers('legacy')Deprecated; doesn't fake DateModern by default since Jest 27
Forget jest.useRealTimers()Subsequent tests inherit fake timersafterEach(jest.useRealTimers)
jest.advanceTimersByTime for async chainsMicrotasks don't drainUse Async variant
Mix real fetch with fake timersFetch resolves at real time; tests raceMock fetch
Hardcoded ms count for "5 minutes"Brittle; magic numbersUse named constants
Sleep loops in test bodyReal time still passes when fake timers are offMock everything time-related
Skip setSystemTime, then call DateDate returns real timeAlways setSystemTime
Tests assume fake-timer state persists across filesPer-test or per-file; doesn'tRe-enable per file

Limitations

  • Modern fake timers are the default since Jest 27. Older projects may still use legacy.
  • doNotFake is fragile. Some functions internally use Date.now() - may behave unexpectedly.
  • DST + TZ interaction is Node-runtime-dependent. ICU data ships with Node.
  • advanceTimersByTime doesn't process Promises. Use the Async variant for promise-chain testing.

References