Fake Timers, Microtasks, and Event Loop Time Manipulation
Manipulating timers, requestAnimationFrame, and system clocks without time drift.
Adetayo Akinsanya (unkletayo) • 2026-10-02
Part 7 in Series — Catch up on the previous article: Mocking Mechanics: vi.fn, vi.spyOn, and Module Hoisting Mechanics (Part 6) before diving into this post.
Testing asynchronous time-dependent code (debounce functions, polling loops, timeout retries) using real delays makes test suites slow and flaky.
Vitest integrates @sinonjs/fake-timers to replace native global timing functions (setTimeout, setInterval, clearTimeout, Date, requestAnimationFrame) with a deterministic virtual clock.
import { test, expect, vi, beforeEach, afterEach } from 'vitest';
function debounce(fn: Function, delay: number) {
let timer: any;
return (...args: any[]) => {
clearTimeout(timer);
timer = setTimeout(() => fn(...args), delay);
};
}
beforeEach(() => {
vi.useFakeTimers();
});
afterEach(() => {
vi.useRealTimers();
});
test('debounces function calls over 500ms', () => {
const func = vi.fn();
const debounced = debounce(func, 500);
debounced();
debounced();
debounced();
// Function shouldn't be called immediately
expect(func).not.toHaveBeenCalled();
# Advance virtual clock by 500ms
vi.advanceTimersByTime(500);
expect(func).toHaveBeenCalledTimes(1);
});
Advancing Time: advanceTimersByTime vs runAllTimers
vi.advanceTimersByTime(ms): Moves the virtual clock forward by exact milliseconds, executing scheduled callbacks within that interval.vi.runAllTimers(): Exhausts all currently queued timers in the event loop queue.vi.setSystemTime(date): Sets the global systemDate.now()to a fixed timestamp for testing time-sensitive logic.
test('sets system date deterministically', () => {
const date = new Date(2026, 8, 8); // Sep 8, 2026
vi.setSystemTime(date);
expect(new Date().getFullYear()).toBe(2026);
});
Up Next in Series →
Part 8: Snapshot Testing Engine: AST Serializers, File Formatting, and Inline Snapshots
Continue to Part 8 →