What fake timers change

A function that uses setTimeout may be simple to run but awkward to test. If a test waits for the actual delay, it becomes slower and can be sensitive to scheduling differences. Fake timers replace the test’s timer clock so you can move it forward explicitly. A 300-millisecond delay can be checked immediately, including what happens just before and exactly when the callback is due.

The examples below use JavaScript in a Node.js test environment with Vitest. They do not need a browser or DOM environment. Put the examples in a file named delay.test.js and run it with Vitest. Fake timers are useful for code whose behavior depends on timers; they do not make unrelated asynchronous work, such as network requests, complete automatically.

Test a debounced callback at its boundary

Debouncing delays a callback until a quiet period has passed. Each new call cancels the previous scheduled callback and starts the delay again. This small implementation takes a callback and a delay, then returns a function that passes the latest arguments to the callback.

import { afterEach, expect, it, vi } from 'vitest'; function debounce(callback, delay) { let timer; return (...args) => { clearTimeout(timer); timer = setTimeout(() => callback(...args), delay); }; } afterEach(() => { vi.clearAllTimers(); vi.useRealTimers(); });

The test first advances almost to the original deadline, then calls the debounced function again. Advancing another 299 milliseconds must not trigger the callback: the second call started a fresh 300-millisecond delay. One further millisecond reaches the new deadline.

it('calls the callback once with the latest value', () => { vi.useFakeTimers(); const callback = vi.fn(); const onChange = debounce(callback, 300); onChange('first'); vi.advanceTimersByTime(199); onChange('latest'); vi.advanceTimersByTime(299); expect(callback).not.toHaveBeenCalled(); vi.advanceTimersByTime(1); expect(callback).toHaveBeenCalledOnce(); expect(callback).toHaveBeenCalledWith('latest'); });

Advance asynchronous timer work safely

A timer callback may settle a promise, after which the function under test continues in a promise reaction. In that case, use Vitest’s asynchronous timer advancement method and await it. Advancing the clock without allowing promise reactions to run can leave an assertion checking the state too early.

async function waitAndUpdate(update) { await new Promise(resolve => setTimeout(resolve, 500)); update('ready'); } it('updates after the delay', async () => { vi.useFakeTimers(); const update = vi.fn(); const task = waitAndUpdate(update); expect(update).not.toHaveBeenCalled(); await vi.advanceTimersByTimeAsync(500); await task; expect(update).toHaveBeenCalledWith('ready'); });

Start the operation before advancing time so its timeout has been scheduled. If the code schedules a timeout only after another asynchronous step, that step must run first. Keep the test focused on an observable result, such as a callback call or changed state, rather than merely asserting that time advanced.

Keep timer tests isolated and meaningful

Enable fake timers before invoking code that schedules a timeout. The afterEach hook in the example discards any outstanding fake timers and restores real timers, so later tests do not inherit a changed clock. If your code offers a cancellation method, test it explicitly: schedule the work, cancel it, advance past its deadline, and assert that the callback was not called. Clearing pending timers during cleanup is not a substitute for testing cancellation behavior.

Choose the smallest time advance that proves each condition. Checking both the instant before a deadline and the deadline itself can reveal off-by-one mistakes. Avoid running every pending timer merely to make a test pass; that can hide an unexpectedly scheduled timeout or cause a repeating timer to keep scheduling work. When a timer interacts with browser rendering, user events, or network responses, fake only the clock-controlled part and arrange the other behavior separately. This keeps the test quick while preserving a clear explanation of what caused the result.

Back to category ↗