Search code examples
javamockingjava-timejmock

How do I simulate the passage of time with java.time.Clock?


Suppose I have this class, ClassToBeTested, where the time elapsed between its method calls are important. foo returns a different value depending on whether you have called foo in the last x hours.

The way I implemented this is to get the current Instant when foo is called, and compare it with the last Instant that foo is called, which I store in a field.

Since ClassToBeTested depends on the current time, I added a Clock field, which callers are required to pass in when they create a ClassToBeTested:

class ClassToBeTested {
    private final Clock clock;
    private Instant lastCalled;

    public ClassToBeTested(Clock clock) {
        this.clock = clock;
    }

    public String foo() {
        if (lastCalled == null || Duration.between(lastCalled, Instant.now(clock)).compareTo(Duration.ofHours(1)) >= 0) {
            lastCalled = Instant.now(clock);
            return "A";
        } else {
            lastCalled = Instant.now(clock);
            return "B";
        }
    }
}

When I am writing my tests though, I realised that there are no factory method that creates a mutable Clock. I tried to create my own mutable clock, so that my tests can look like this:

private final FakeClock fakeClock = new FakeClock();

@Test
public void fooReturnsAAfterAnHour() {
    var myInstance = new ClassToTest(fakeClock);
    assertThat(myInstance.foo(), is("A"));
    fakeClock.goForward1Hour(); // this mutates the clock, and changes what instant() will return
    assertThat(myInstance.foo(), is("A"))
}

@Test
public void fooReturnsBWithinAnHour() {
    var myInstance = new ClassToTest(fakeClock);
    assertThat(myInstance.foo(), is("A"));
    fakeClock.goForward30Minutes(); // this mutates the clock, and changes what instant() will return
    assertThat(myInstance.foo(), is("B"))
}

only to find that in the documentation, it says:

All implementations that can be instantiated must be final, immutable and thread-safe.

So it seems like a mutable implementation of Clock is incorrect. But looking up posts on stack overflow, many suggest using a mutable clock (example). Some also suggest mocking the Clock abstract class. When I tried that out, jMock (which is what I'm using) doesn't like it because Clock is not an interface. Apparently, if I want to mock classes, I'd have to include another dependency, which is mainly used for mocking legacy code. That doesn't sound appropriate for mocking Clock to me.

I can also add a setter for the clock field in ClassToBeTested, so that I can do:

myInstance.setClock(Clock.offset(clock, Duration.ofHours(1)));

to advance the time. But I think this breaks encapsulation.

And at this point, I'm out of ideas. What can I do?


Solution

  • Your FakeClock approach is fine. That's the approach I'd use.

    If you prefer to use an implementation from a library instead of your own, you could use MutableClock from the threeten-extra library. That class was designed for exactly this use case.

    The "immutable" requirement was removed from the Clock class in Java 17. It was removed not because of changes being made to Clock's implementation, but because the requirement was harmfully restrictive (see your use case) while not serving a useful purpose. So if you're using Java <17, I'd say it's safe to simply ignore that requirement.