Mocking time
Code that calls Date.now(), time.time(), or datetime.now() directly
reads a value that changes every run, so any test over it is a coin flip near
a boundary and silently wrong across a daylight-saving shift. The fix is not
sprinkling sleeps or widening tolerances: it is making time an input you
control, then pinning it to the exact instant the test cares about.
Method
- Inject the clock, never reach for the global. Pass a time source into
the unit: a
Clock interface, a now: () -> datetime callable, or a
constructor argument. Production wires the real clock, tests wire a fake.
A function that closes over the global module clock cannot be frozen from
the outside.
- Freeze now with a purpose-built library. Use freezegun
(
@freeze_time("2026-03-08T12:00:00Z")), Sinon fake timers
(sinon.useFakeTimers), or Clock.fixed(instant, zone) on the JVM. Set
one explicit instant per test so the assertion reads against a literal you
can see, not "roughly today".
- Advance time on purpose, do not sleep. For timeouts, retries, and
debounces, tick the fake forward (
clock.tick(30_000),
frozen.move_to(...)) and assert the effect fired. A real sleep(30)
makes the suite slow and still races; a controlled tick is instant and
exact.
- Store and compare in UTC, render in a zone. Keep every persisted and
compared timestamp in UTC. Convert to a local zone only at display, and
assert on the UTC value so a test that passes in one CI region passes in
all of them.
- Pin real timezones for the cases that bite. Test a US/Pacific spring
DST gap (a wall time of 02:30 that does not exist), a fall overlap (01:30
occurring twice), and a non-hour offset like Asia/Kolkata. Use named IANA
zones (
ZoneInfo("America/New_York")), never a fixed -05:00, so the
rules travel with the data.
- Cover the ugly boundaries explicitly. Add cases for midnight rollover,
month and year ends, February 29, and epoch-second overflow if you touch
32-bit time. These are where off-by-one date math surfaces.
Litmus tests
- Grep the code under test for
now(, today(, Date.now, time.time: any
hit that is not the injected source is a hole.
- Does the suite pass with the machine clock set to December 31, 23:59 and
again in a +13 timezone?
- Can you read the expected instant as a literal in each assertion?
Boundaries
This covers making time deterministic in a test, not scheduling or cron
correctness in production, which needs its own integration coverage. Follow
the clock abstraction a project already has rather than introducing a second
one.
1---2name: mocking-time3description: Control the clock in tests through an injectable time source so "now" is frozen and timezone behavior is explicit. Use when code reads the wall clock and a flaky or time-dependent test needs deterministic results.4---56# Mocking time78Code that calls `Date.now()`, `time.time()`, or `datetime.now()` directly9reads a value that changes every run, so any test over it is a coin flip near10a boundary and silently wrong across a daylight-saving shift. The fix is not11sprinkling sleeps or widening tolerances: it is making time an input you12control, then pinning it to the exact instant the test cares about.1314## Method15161. **Inject the clock, never reach for the global.** Pass a time source into17 the unit: a `Clock` interface, a `now: () -> datetime` callable, or a18 constructor argument. Production wires the real clock, tests wire a fake.19 A function that closes over the global module clock cannot be frozen from20 the outside.212. **Freeze now with a purpose-built library.** Use freezegun22 (`@freeze_time("2026-03-08T12:00:00Z")`), Sinon fake timers23 (`sinon.useFakeTimers`), or `Clock.fixed(instant, zone)` on the JVM. Set24 one explicit instant per test so the assertion reads against a literal you25 can see, not "roughly today".263. **Advance time on purpose, do not sleep.** For timeouts, retries, and27 debounces, tick the fake forward (`clock.tick(30_000)`,28 `frozen.move_to(...)`) and assert the effect fired. A real `sleep(30)`29 makes the suite slow and still races; a controlled tick is instant and30 exact.314. **Store and compare in UTC, render in a zone.** Keep every persisted and32 compared timestamp in UTC. Convert to a local zone only at display, and33 assert on the UTC value so a test that passes in one CI region passes in34 all of them.355. **Pin real timezones for the cases that bite.** Test a US/Pacific spring36 DST gap (a wall time of 02:30 that does not exist), a fall overlap (01:3037 occurring twice), and a non-hour offset like Asia/Kolkata. Use named IANA38 zones (`ZoneInfo("America/New_York")`), never a fixed `-05:00`, so the39 rules travel with the data.406. **Cover the ugly boundaries explicitly.** Add cases for midnight rollover,41 month and year ends, February 29, and epoch-second overflow if you touch42 32-bit time. These are where off-by-one date math surfaces.4344## Litmus tests4546- Grep the code under test for `now(`, `today(`, `Date.now`, `time.time`: any47 hit that is not the injected source is a hole.48- Does the suite pass with the machine clock set to December 31, 23:59 and49 again in a +13 timezone?50- Can you read the expected instant as a literal in each assertion?5152## Boundaries5354This covers making time deterministic in a test, not scheduling or cron55correctness in production, which needs its own integration coverage. Follow56the clock abstraction a project already has rather than introducing a second57one.