Serguey Asael Shinder: Make the clock an argument
Some bugs are scheduled. A certificate expires on a known day, a key rotates on a published date, a month has 31 days, the clocks go back an hour at the end of October. The code that handles those moments is often the code nobody has ever run at those moments, because it reads the current time from the machine and the tests run on whatever day the build happens.
The pattern is easy to recognise:
boolean isExpired(Token t) {
return Instant.now().isAfter(t.expiresAt());
}
There is no way to ask this method what it will answer next Tuesday, short of waiting until next Tuesday.

The change is one parameter. Java has had an answer since Java 8: java.time.Clock. Its javadoc says it plainly — the purpose of the class is to allow alternate clocks to be plugged in as and when required, and the best practice is to pass a Clock into any method that needs the current instant (https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/time/Clock.html).
final class TokenCheck {
private final Clock clock;
TokenCheck(Clock clock) { this.clock = clock; }
boolean isExpired(Token t) {
return Instant.now(clock).isAfter(t.expiresAt());
}
}
Production wires in Clock.systemUTC(). Tests use Clock.fixed(...) to stand on any instant they like, or Clock.offset(...) to move time forward without sleeping. Every now() method in java.time — Instant.now(clock), LocalDate.now(clock), ZonedDateTime.now(clock) — takes one.
What it buys. The dates that break code become test inputs instead of events:
- the last second before an expiry and the first second after it;
- 31 January plus one month, and 29 February;
- the night a zone leaves summer time, when a local hour happens twice;
- the day a rotation, a renewal or a contract change takes effect — the date is usually known in advance, and the test can be written the day it is announced rather than the day it fails.
What it does not buy. A fixed clock does not make the rest of the system agree with it. Databases, message brokers and other services have their own clocks, and the time a test fixes inside one process is not the time anything else sees. It also does not fix code that converts between time zones carelessly; it only lets you test that code at the moments where it matters.
The rule I use. Business logic never calls a no-argument now(). It receives a Clock, or it receives the instant itself as a parameter, decided once at the edge of the request. The second option is often better: one request, one "now", no chance that two parts of the same operation see two different times.
Time is an input. Code that treats it as one can be asked about any day of the year — including the ones that have not happened yet.