The test that fails at midnight
Real time in a test means: a test that waits, a test that is different at the end of the month, a test that fails when the machine is slow, and a test nobody can reason about. The cure is always the same — the code asks something for the time, rather than taking it.
function isExpired(quote) {
return Date.now() - quote.issuedAt > 30 * 24 * 60 * 60 * 1000;
}
it("expires after thirty days", () => {
const quote = { issuedAt: Date.now() - 31 * 24 * 60 * 60 * 1000 };
expect(isExpired(quote)).toBe(true); // fine — but the test depends on the real clock
});
Inject the clock
Two lines of production code, and the test becomes an exact statement about a boundary. This is the same move as the dependency injection in the last lesson, applied to the most-used global in the language.
export function isExpired(quote, now = Date.now()) {
return now - quote.issuedAt > THIRTY_DAYS;
}
it("expires after thirty days", () => {
expect(isExpired({ issuedAt: 0 }, THIRTY_DAYS + 1)).toBe(true);
expect(isExpired({ issuedAt: 0 }, THIRTY_DAYS - 1)).toBe(false);
});
Fake timers, for code that schedules
Every real runner has this — vi.useFakeTimers(), jest.useFakeTimers() — and it replaces setTimeout, setInterval and Date.now globally for the duration of the test. The effect is the same: a debounce test that takes a microsecond and proves the boundary, instead of one that sleeps 300 ms and proves roughly nothing.
const clock = fakeTimers(0);
const save = spy();
const debounced = debounce(save, 300, clock);
debounced("a");
debounced("b");
clock.tick(299);
expect(save.callCount).toBe(0); // not yet
clock.tick(1);
expect(save.callCount).toBe(1); // exactly once
expect(save.calledWith("b")).toBe(true);
Fake timers are global state. A suite that installs them and forgets to restore leaves every later test in a world where setTimeout never fires — which shows up as a completely unrelated test hanging. afterEach(() => vi.useRealTimers()).
Randomness
The same applies to ids and tokens: crypto.randomUUID() in the middle of a function is untestable, and a function that takes an id generator is testable and no harder to call.
// the code takes its source of randomness
export function pickFitter(fitters, random = Math.random) {
return fitters[Math.floor(random() * fitters.length)];
}
// the test decides what "random" means
expect(pickFitter(["a", "b", "c"], () => 0)).toBe("a");
expect(pickFitter(["a", "b", "c"], () => 0.99)).toBe("c");
// or a seeded generator, when you need a whole sequence
const random = seededRandom(42);
Time zones and formatting
A test that asserts a formatted date fails on a machine in another zone unless the zone is fixed. Set TZ=UTC in the test environment, store everything in UTC as the web course said, and assert on the ISO string rather than the localised one — or pass an explicit locale and zone to Intl.DateTimeFormat and assert that.