Code that calls Date.now(), new Date() or setTimeout directly is hard to test: you cannot control the value, and advancing real time makes tests slow and flaky. Injecting a clock makes time a dependency you can freeze and advance deterministically.
Before you start
You should be comfortable with dependency injection and fake timers. This article covers clock injection and time control.
Step-by-step walkthrough
Step 1: Pass time in, do not read it
Define a clock interface with methods such as now(), and inject it. The production clock returns the real time, and the test clock returns a fixed value. The code then has no hidden dependency on the system clock.
Step 2: Freeze and advance deterministically
In tests, use a fake clock or the framework’s fake timers to set a fixed start and advance it explicitly. Timeouts, expirations and TTLs become assertions about what happens after you advance, with no real waiting.
Step 3: Remove real sleeps
A test that sleeps to wait for a timer is slow and flaky, because the sleep must be long enough on a slow machine. With a fake clock you advance time instantly, so the test is fast and deterministic.
Worked scenario
The fake clock controls expiry.
function isExpired(record, clock) {
return clock.now() > record.expiresAt;
}
test('token expires', () => {
let t = 1000;
const clock = { now: () => t };
const token = { expiresAt: 2000 };
expect(isExpired(token, clock)).toBe(false);
t = 3000;
expect(isExpired(token, clock)).toBe(true);
});Walk through the example
isExpired reads time from the injected clock, so the test sets t and asserts. There is no sleep and no dependence on the machine’s clock. Advancing t directly simulates the passage of time with full control.
Common mistake
Calling Date.now() inside logic, so tests must sleep and remain flaky, or asserting on an exact formatted time that changes with the timezone. Another is using real timers and hoping the machine is fast enough.
Verify the behavior
Assert behavior at several clock values with no sleeps. Run the test under load and confirm it stays fast and stable. Confirm the production wiring passes the real clock and the test passes the fake.
Interview exercise
Why does injecting a clock make tests deterministic?
Answer and reasoning
Because time becomes an explicit input the test controls, rather than an ambient dependency that changes on every run. The test sets a known value and advances it deliberately, so the result is the same every time. Real-time dependence, which varies with machine speed and load, is removed.
Continue learning
Compare timers in Fake timers and determinism in Async determinism. Read the Martin Fowler on clock wrappers and try the Testing interview questions.