← Recipes

阅读中文版本

Mock Time with Sinon Fake Timers

Control time in tests with fake timers so debounce, retry, and timeout logic is deterministic instead of flaky.

~25 mintestingjavascripttimers

Task

Use fake timers to advance time deterministically and test debounce and retry behaviour.

When to use this

A debounce test is flaky because it depends on real timers and race conditions with CI load.

Prerequisites

  • A test runner with sinon or an equivalent fake-timer API
  • Code using timers
  • A debounced function under test

Steps

1. Install fake timers before the test and advance them explicitly.

const clock = sinon.useFakeTimers();
let calls = 0;
const debounced = debounce(() => calls++, 300);
debounced(); debounced(); clock.tick(299);
expect(calls).toBe(0);
clock.tick(1);
expect(calls).toBe(1);
clock.restore();

Expected output: The debounce fires exactly once after the window, with no real waiting.

2. Restore timers after each test.

afterEach(() => sinon.restore());

Expected output: Fake timers cannot leak into other tests and cause confusing failures.

Troubleshooting

Problem

Promises never resolve under fake timers

Solution

Flush microtasks with await Promise.resolve() between ticks, or use a timer API that supports async.

Problem

Tests hang

Solution

Always restore timers in afterEach so real timers return.

Limitations

  • Fake timers do not cover real I/O timing.
  • Microtask ordering needs care with promises.
  • Over-mocking time can hide real delays.

What this recipe does

Use fake timers to advance time deterministically and test debounce and retry behaviour.

A debounce test is flaky because it depends on real timers and race conditions with CI load.

Prerequisites

  • A test runner with sinon or an equivalent fake-timer API
  • Code using timers
  • A debounced function under test

Procedure

  1. Install fake timers before the test and advance them explicitly.
const clock = sinon.useFakeTimers();
let calls = 0;
const debounced = debounce(() => calls++, 300);
debounced(); debounced(); clock.tick(299);
expect(calls).toBe(0);
clock.tick(1);
expect(calls).toBe(1);
clock.restore();

Expected result: The debounce fires exactly once after the window, with no real waiting.

  1. Restore timers after each test.
afterEach(() => sinon.restore());

Expected result: Fake timers cannot leak into other tests and cause confusing failures.

How to confirm it worked

Run the commands above and confirm every expected result in order. If a step does not produce the stated output, use the troubleshooting notes before continuing. A passing run shows the debounce test passes deterministically across many runs and fails when the window constant changes unexpectedly.

Troubleshooting

Promises never resolve under fake timers — Flush microtasks with await Promise.resolve() between ticks, or use a timer API that supports async.

Tests hang — Always restore timers in afterEach so real timers return.

Limitations

  • Fake timers do not cover real I/O timing.
  • Microtask ordering needs care with promises.
  • Over-mocking time can hide real delays.