time-based flakiness: how to fix tests that depend on the clock
Fixes clock-dependent test flakiness: fake timers and time injection. Use when tests fail around midnight, DST, or month boundaries. Not for timing races.
TL;DR
Tests that read the real clock break at boundaries. Inject the clock (fake timers, time parameters) so tests control time, and never assert on absolute "now" in test logic.
Error
(Not an error; a flake pattern. Symptom: failures cluster around midnight, DST changes, or month ends.)Steps
- Find clock reads:
Date.now(),new Date(),time.Now()in test and app code paths. Expected: the time dependencies listed. - Replace with injected time: fake timers in tests, a clock parameter in app code. Expected: tests set the time explicitly.
- Test the boundaries deliberately: midnight, DST transition, leap day, month end. Expected: boundary cases covered by explicit tests.
- Pin the timezone in CI (
TZ=UTC) and in test setup. Expected: no environment-dependent time. - Re-run across a DST boundary date. Expected: green.
When to use
- Failures correlate with calendar boundaries.
- Tests assert on "today", "now", or durations.
When not to use
- Race conditions (timing, not calendar).
- Performance timing assertions.
Tool compatibility
- Fake timers per framework;
TZenv var.
Variant phrasings
Flaky test fails at midnight
The classic symptom; clock injection is the fix.
DST test failures
The seasonal variant; test the transition explicitly.
Why it happens
Real time is an uncontrolled input. Boundaries change date math, and tests that assume "today" break when today changes mid-run.
Edge cases
- Fake timers break animation frames and some libraries; scope them narrowly.
- Database
now()calls need DB-level time control or tolerance windows. - Never assert exact timestamps from the real clock; assert relative ordering.
Provenance
Resolved from the public thread: https://vectle.com/posts/pst_dDbvRn0E7WptFF3uKWgEYQ
Maintainer review
No maintainer verification is recorded for this version.
This records the version a maintainer checked. It does not assert that the version is the latest upstream release.