edgegoat Guides
How to mock Date in JavaScript tests
Code that reads the current time is untestable until you control the clock. You do not always need fake timers: often you only need new Date() to return one specific edge instant.
Freeze the clock for one function call
import { withFrozenTime } from 'edgegoat';
withFrozenTime('2026-12-31T23:59:59.999Z', () => {
Date.now(); // 1798761599999
new Date(); // 2026-12-31T23:59:59.999Z
new Date(2020, 0); // still works normally
});It accepts a Date, an ISO string or a timestamp, works with any test runner, restores the real Date afterwards (also when the function throws or its promise rejects) and leaves timers alone.
The bug: "is this today?" in UTC
function isToday(date) {
return date.toISOString().slice(0, 10) === new Date().toISOString().slice(0, 10);
}It compares UTC calendar days. At 23:30 in New York, 00:30 the same evening is "yesterday" in UTC terms. Test it with the clock frozen late in the evening, in every zone from timeZones():
import { expect, test } from 'vitest';
import { timeZones, withFrozenTime, withTimeZone } from 'edgegoat';
test.each(timeZones())('$label', ({ value: zone }) => {
withTimeZone(zone, () => {
const now = new Date(2026, 11, 31, 23, 30);
const earlierToday = new Date(2026, 11, 31, 0, 30);
withFrozenTime(now, () => expect(isToday(earlierToday)).toBe(true));
});
});Intl.DateTimeFormat in the user's zone.How it works, and its limits
withFrozenTime swaps globalThis.Date for a thin wrapper whose prototype is the real one, so instanceof Date, Date.UTC, Date.parse, subclasses and new Date(value) keep working. Only Date.now(), new Date() and Date() are frozen.
Date or Date.now before the call still sees real time, and performance.now() and timers are not touched. When you need to advance time or run timers, use your runner's fake timers instead.When to use fake timers instead
- Jest:
jest.useFakeTimers({ now: date })orjest.setSystemTime(date). - Vitest:
vi.useFakeTimers()thenvi.setSystemTime(date). - node:test:
mock.timersin recent Node.js versions, including theDateAPI.
Fake timers are the right tool for debounce, intervals and timeouts. For "what does this function do at 23:59:59.999 on New Year's Eve", a frozen clock is simpler.
FAQ
Use jest.useFakeTimers({ now: date }), or withFrozenTime(date, fn) from edgegoat if you only need a fixed "now" without fake timers.
vi.setSystemTime(date) with fake timers enabled. vi.spyOn(Date, 'now') only covers Date.now(), while withFrozenTime also freezes new Date().
No. It only replaces Date. Timers, performance.now() and the event loop run normally.
Yes. Nest them: withTimeZone(zone, () => withFrozenTime(date, fn)). Both restore their state when the function returns.
More guides
- How to test time zones in JavaScript
- How to test daylight saving time in Jest
- Unicode test strings for JavaScript
- JavaScript number edge cases to test
- Testing dates before 1970 in JavaScript
- Negative zero in JavaScript: why -0 is different from 0
- Property based testing and curated edge cases in JavaScript