Controlling Time in Jest Tests with useFakeTimers
Learn how Jest's useFakeTimers lets you control Date, setTimeout, and clearTimeout for deterministic, fast tests-complete with a debounce example and cleanup tips.
20 Jun 2026, 19:13 UTC

Why timing makes tests flaky
When code under test uses setTimeout, setInterval or reads Date.now(), the outcome depends on the real clock. A test that passes on a fast CI runner can fail on a slower machine because callbacks fire at unpredictable moments. That nondeterminism pushes developers toward arbitrary waits or skipped tests, which hurts confidence and slows feedback.
The thesis: fake timers put you in charge of the clock
jest.useFakeTimers() replaces the global timer functions and Date with mock implementations, so time only moves when you tell it to. You can tick the clock forward, run every pending callback, or pin a specific date - all without waiting for real seconds to elapse. The one rule: restore real timers when the test finishes so nothing leaks into other tests.
Enabling and scoping fake timers
Call jest.useFakeTimers() at the start of a test (or in a beforeEach). By default it mocks both the timer functions and the date module. If you only need part of that, narrow the scope with toFake:
// mock only setTimeout and clearTimeout, leave Date alone
jest.useFakeTimers({ toFake: ['setTimeout'] });
After this call, setTimeout queues callbacks on a mock clock instead of the real event loop, and Date.now() returns the mocked time (0 by default, i.e. the Unix epoch). To pin a specific date, use jest.setSystemTime(new Date('2026-03-01T12:00:00Z')).
Advancing the mock clock
Two helper methods move time forward:
jest.advanceTimersByTime(ms)- increments the mock clock by the given milliseconds and executes any callbacks whose delay has elapsed.jest.runAllTimers()- runs every pending timer until the queue is empty (useful when you don't know the exact delay).
Both calls are synchronous; the test proceeds immediately after they finish.
Worked example: testing a debounce utility
Consider a simple debounce function that returns a wrapper which postpones execution until wait milliseconds have passed without another call:
function debounce(fn, wait) {
let timeout;
return function (...args) {
clearTimeout(timeout);
timeout = setTimeout(() => fn.apply(this, args), wait);
};
}
To verify that the wrapped function is called only after the wait period, fake the timers:
test('debounce calls fn after wait milliseconds', () => {
jest.useFakeTimers();
const mockFn = jest.fn();
const debounced = debounce(mockFn, 300);
// Invoke multiple times quickly
debounced();
debounced();
debounced();
// No call yet because the wait hasn't elapsed
expect(mockFn).not.toHaveBeenCalled();
// Fast-forward the mock clock by 300 ms
jest.advanceTimersByTime(300);
// The original function should have been invoked exactly once
expect(mockFn).toHaveBeenCalledTimes(1);
// Clean up so other tests see real timers
jest.useRealTimers();
});
This test passes reliably on any machine because the test controls exactly when the mock clock moves. A quick way to verify the setup works in your project: assert that Date.now() returns a fixed value right after jest.useFakeTimers(), and that a setTimeout callback fires only after advanceTimersByTime.
Trade-off: cleanup is mandatory
If you forget to call jest.useRealTimers(), the fake timers persist and can affect later tests that depend on real setTimeout behavior. Symptoms include tests that hang or fail with unexpected timing errors. The safest pattern is to restore real timers in a teardown block:
afterEach(() => {
jest.useRealTimers();
});
Another limitation: runAllTimers() assumes the timer chain eventually ends. Code that reschedules itself indefinitely (like a polling loop) will make it throw, so prefer advanceTimersByTime for those cases.
Actionable closing
Add jest.useFakeTimers() to the tests that interact with timers, advance the clock with advanceTimersByTime or runAllTimers, and always restore real timers afterward. Timer-mocking defaults have changed across Jest major versions (modern fake timers became the default in Jest 27), so confirm the behavior of the version your project uses, and run the suite with --verbose to catch deprecation warnings. With this approach, time-dependent code becomes as predictable as synchronous logic, eliminating flaky waits and giving you fast, reliable feedback.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.