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?