CtrlK
BlogDocsLog inGet started
Tessl Logo

testland/fake-clock-testing

Fake clocks / freeze time in tests across every mainstream runtime: freezegun (Python), Jest fake timers + Sinon @sinonjs/fake-timers (JS/TS), timecop (Ruby), java.time.Clock / InstantSource injection (JVM), .NET TimeProvider / FakeTimeProvider, and libfaketime (LD_PRELOAD for any native binary). Covers the language-agnostic discipline - inject or patch the clock, freeze vs tick vs advance vs set-system-time semantics, teardown so fake clocks never leak between tests - plus the shared anti-pattern table (real sleep under a frozen clock, leaked clock state, timezone-dependent assertions). Per-library setup, API, and CI recipes live in references/{python,js,ruby,jvm,dotnet,libfaketime}.md. Use when tests need deterministic control of now(), timers, or timeouts in any language, or when choosing the right fake-clock tool for a stack.

93

Quality

93%

Does it follow best practices?

Impact

Average score across 3 eval scenarios

SecuritybySnyk

Passed

No findings from the security scan

Overview
Quality
Evals
Security
Files

jvm.mdreferences/

JVM - java.time.Clock and InstantSource injection

The JVM has no "freeze clock" library because java.time (Java 8+) was designed with dependency-injected Clock as the testing pattern. Per docs.oracle.com Clock: "Most application code should inject a Clock into any method that needs the current instant and date/time." Production injects Clock.systemDefaultZone(); tests inject Clock.fixed(...). No global monkey-patching.

The injection pattern

public class TaskScheduler {
    private final Clock clock;
    public TaskScheduler(Clock clock) { this.clock = clock; }
    public Task scheduleNext(Duration after) {
        return new Task(Instant.now(clock).plus(after));
    }
}
// production wiring
TaskScheduler prod = new TaskScheduler(Clock.systemDefaultZone());

Clock.fixed (frozen)

@Test
void scheduleNext() {
    Clock fixed = Clock.fixed(Instant.parse("2026-05-20T14:30:00Z"),
                              ZoneId.of("America/New_York"));
    Task task = new TaskScheduler(fixed).scheduleNext(Duration.ofMinutes(5));
    assertEquals(Instant.parse("2026-05-20T14:35:00Z"), task.getScheduledAt());
}

Clock.fixed never advances - successive Instant.now(fixed) calls return the same value.

Clock.offset (relative) and a mutable test clock

Clock realPlus10 = Clock.offset(Clock.systemDefaultZone(), Duration.ofMinutes(10));

For advance-mid-test semantics, a small custom clock:

public class MutableClock extends Clock {
    private Instant instant;
    private final ZoneId zone;
    public MutableClock(Instant instant, ZoneId zone) { this.instant = instant; this.zone = zone; }
    public void setInstant(Instant i) { this.instant = i; }
    public void advance(Duration d) { instant = instant.plus(d); }
    @Override public Clock withZone(ZoneId z) { return new MutableClock(instant, z); }
    @Override public ZoneId getZone() { return zone; }
    @Override public Instant instant() { return instant; }
}

InstantSource (Java 17+)

Per docs.oracle.com InstantSource, a narrower interface than Clock (just instant(), no zone) - prefer it when code only needs the instant; a lambda is a complete fake:

InstantSource fake = () -> Instant.parse("2026-05-20T14:30:00Z");

Spring DI integration

@Configuration
public class ClockConfig {
    @Bean public Clock clock() { return Clock.systemDefaultZone(); }
}

@TestConfiguration
public class TestClockConfig {
    @Bean public Clock clock() {
        return Clock.fixed(Instant.parse("2026-05-20T14:30:00Z"), ZoneOffset.UTC);
    }
}

DST tests

Clock fixed = Clock.fixed(Instant.parse("2026-03-08T07:30:00Z"),  // 02:30 local - non-existent
                          ZoneId.of("America/New_York"));
ZonedDateTime zdt = ZonedDateTime.ofInstant(fixed.instant(), fixed.getZone());
// assert per the resolver rules documented in dst-transition-reference

Anti-patterns

Anti-patternWhy it failsFix
Instant.now() / System.currentTimeMillis() directlyNot injectableInject Clock; Instant.now(clock)
Static-mocking Clock with PowerMockBrittle bytecode rewritingUse DI
No zone in Clock.fixedDefaults matter; local-time tests degenerateAlways pass the zone
Only frozen clocks, never advancingDuration arithmetic untestedMutableClock.advance
Multiple Clocks per serviceCoordination bugsOne Clock per service

Limitations

  • Requires source control - libraries calling Instant.now() internally aren't reachable; libfaketime partially applies but some JVM time calls bypass libc (libfaketime.md).
  • Thread.sleep is real time - use a controllable ScheduledExecutorService for schedule-driven code.

References

SKILL.md

tile.json