Ch. 22 · Software Testing

Testing Time with an Injected Clock

Inject a clock instead of calling the system time, freeze it in tests, and remove the sleeps that make tests flaky.

~2 min readadvancedupdated Oct 5, 2026

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);
});
JavaScript

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.

More in Software Testing

read ✓Software Testing · mid

Code Coverage and Its Limits

Read line and branch coverage as a signal, not a goal, and avoid the tests that chase the number without checking behavior.

~2 min readread →
read ✓Software Testing · hard

Scoping End-to-End Tests

Reserve end-to-end tests for critical user journeys, use stable selectors, and avoid duplicating coverage the lower tests already have.

~2 min readread →
read ✓Software Testing · mid

Testing Error Paths

Test failures and edge inputs deliberately, assert the error type and message, and stop the happy path from hiding bugs.

~2 min readread →
esc