@sinonjs/fake-timers
Deterministic fake implementations of setTimeout, setInterval, Date, and other timer APIs for fast, reliable JavaScript tests.
Repository Health
Technical Analysis
@sinonjs/fake-timers replaces the timer and date APIs in JavaScript environments with controllable fakes, letting tests advance the clock manually instead of waiting on real wall-clock time. It reimplements setTimeout, setInterval, setImmediate, requestAnimationFrame, requestIdleCallback, process.nextTick, process.hrtime, performance, and Date, all driven by a single virtual clock that only moves when the test tells it to.
Originally extracted from Sinon.JS and now used as its timer-faking engine, the library also ships as a standalone package with framework-agnostic hooks: install() patches the global object, tick() and tickAsync() advance time synchronously or with the event loop, and newer tick modes (nextAsync, interval) support automatic advancement for code that doesn’t expose an obvious point to call tick().
What You Get
- A createClock() API for a standalone virtual clock, plus install()/withGlobal() to patch timers onto the global object or an arbitrary context
- Fakes for setTimeout, setInterval, setImmediate, requestAnimationFrame, requestIdleCallback, queueMicrotask, process.nextTick, process.hrtime, performance.now, and Date
- Synchronous (tick) and async-aware (tickAsync, runAllAsync, nextAsync tick mode) clock advancement for code with pending promises between timers
- Built-in TypeScript type definitions generated from JSDoc, with no separate @types package needed
- Configurable toFake/toNotFake lists so only specific timer APIs are hijacked, leaving the rest native
Common Use Cases
- Testing debounce/throttle utilities without slowing down the test suite with real delays
- Verifying retry-with-backoff or polling logic across many simulated intervals in milliseconds of real test time
- Asserting on animation-frame or idle-callback driven UI code in a headless test environment
- Snapshotting time-dependent output (e.g. Date.now()-derived IDs or timestamps) with a frozen, reproducible clock
Under The Hood
Architecture
The library is built as a single withGlobal(_global) factory (src/fake-timers-src.js) that closes over a target global object and returns createClock/install; every fake (Date, performance, Intl, Temporal) is constructed inside that closure so it can reach back into the owning clock. Timer bookkeeping is split across two structures kept in sync by ensureTimerState: a Map from timer id to timer object for O(1) clearTimeout/clearInterval, and a TimerHeap min-heap ordered by callAt for O(log n) scheduling and O(1) lookup of the next timer to fire via peek(). addTimer, firstTimerInRange, and the heap’s bubbleUp/bubbleDown methods are the core primitives everything else (tick, tickAsync, runAll, next) is built on top of; replacing that heap would mean rewriting most of the scheduling logic that depends on its ordering guarantees.
Tech Stack
The runtime dependency surface is minimal: only @sinonjs/commons (for a safe reference to the global object) at ^3.0.1. Browser distribution goes through esbuild via @mochify/cli, which also drives cross-browser testing (Chrome via puppeteer, Edge/Firefox/Safari through mochify.webdriver.js). TypeScript consumers get types generated straight from JSDoc using @typescript/native-preview (tsgo) rather than a hand-maintained .d.ts or a parallel TypeScript source tree. Tooling is ESLint (via @sinonjs/eslint-config) plus Prettier, wired into husky/lint-staged pre-commit hooks and a GitHub Actions workflow that separately runs lint, prettier, node tests, and a watch-mode sanity check.
Code Quality
The test suite (test/fake-timers-test.js plus per-issue regression files under test/) contains roughly 540 describe/it blocks run via Mocha, with coverage enforced through nyc thresholds (85% branches, 92% lines/functions/statements) and a dedicated test-check-coverage script. Source is annotated throughout with JSDoc @typedef/@callback/@param blocks that double as the type-generation input, and the few catch { // ignored } blocks are narrowly scoped, commented feature-detection fallbacks rather than swallowed errors. Naming is consistent and the heap implementation carries explanatory comments on why two data structures (Map + heap) are maintained together.
What Makes It Unique
Most fake-timer libraries stop at setTimeout/setInterval; this one additionally fakes process.hrtime, performance.now, Intl, and (on supporting runtimes) the Temporal API, all synchronized to the same virtual clock. Its scheduler uses an explicit min-heap rather than a sorted array or naive scan, and it offers multiple tick strategies — synchronous tick, promise-aware tickAsync/nextAsync tick mode, and a real-interval-driven shouldAdvanceTime mode — to fit different async code shapes rather than forcing one ticking model on every consumer.
Used by 2 apps in this directory
Metabase
Analytics
The open-source BI platform that lets anyone ask questions and build dashboards without writing SQL — with an embedded analytics SDK and AI-powered query assistant included.
Zulip
Team Chat
Topic-based team chat that brings the structure of email threads to real-time messaging, so distributed teams never lose context across hundreds of concurrent conversations.