How to mock java Clock with CDI
Timing tests are always a pain, in particular because it is very tempting to use Thread.sleep() and then your tests will easily be flacky.
However since java 8, there is the Clock API which enables to be a bit more flexible. Let see how to use it to make the tests way more reliable.
Your own Clock
The first need in a test is generally to become the master of time (i.e. be able to make the time moving at the rate your want). To illustrate that, here is a sample test representing not that bad all test harnessing window based implementations (rate limiting, circuit breaker, batch, ...):
@Test
void doTest() {
doSomethingInCurrentWindow();
moveToNextWindow();
doSomethingInNextWindow();
}
Indeed, moveToNextWindow() could be a Thread.sleep() but how convenient it would be to just be a setTime(now + windowDuration)?
Clock API enables to do that by creating a custom window:
public class CustomClock extends Clock { // <1>
@Setter // lombok generates setInstant(Instant)
private Instant instant; // <2>
private ZoneId zone = ZoneId.of("UTC");
@Override
public ZoneId getZone() {
return zone;
}
@Override
public Clock withZone(final ZoneId zone) { // <3>
this.zone = zone;
return this;
}
@Override
public Instant instant() { // <4>
return instant.atZone(zone);
}
}
- First we extend java Clock to override the part we want,
- We ensure the Instant of the clock is settable from outside (note this implementation is not thread safe which is generally ok in tests but not for runtime/replay algorithms),
- Small warning that this implementation should create a new custom clock, if you know your application don't use that you can implement it this way or just throw an UnsupportedOperationException,
- Finally all that enables us to return the instance we set manually.
If we go back to our previous test, it can now look like:
@Test
void doTest() {
final CustomClock clock = new CustomClock();
clock.setInstant(Instant.now());
doSomethingInCurrentWindow(clock); // <1>
clock.setInstant(clock.instant().plusMillis(windowDurationMs)); // <2>
doSomethingInNextWindow(clock); // <1>
}
- Everything is the almost same except we now pass to the doSomething() method the clock to ensure the logic uses our own clock implementation,
- And we can move in time as we need without having to wait for the same amount of time (concretely even if the window duration is 1 year, and assuming the doSomething() logic is fast, this test will take a couple of ms.
So the issue we have today is how to ensure the logic uses that clock. There are two main cases:
- You wire everything manually so it will use that clock (this is generally the case for generic libraries),
- You use an IoC (CDI, Spring, Guice, ...) and you inject the clock.
This is this last case we will consider now.
Clock and CDI
There are several ways to get a clock instance in CDI but the easiest is likely to create a producer:
@ApplicationScoped
public class ClockFactory {
@Produces
public Clock clock() {
return Clock.systemUTC();
}
}
- This is a plain standard producer which will enable to use @inject Clock clock anywhere in the application without having to say how the clock was constructed (no more usage of the Clock factory classes),
- You probably noted the producer does not define any qualifier, in real applications it is likely better (@MyApplication) because Clock is a JVM type and these types can easily conflict between libraries (compared to application types which are not ambiguous at all),
- There is no explicit scope on the clock producer so it will be @Dependent, which means it is created for each injection/lookup. For this one it is not important since systemUTC is a constant already and it enables to bypass CDI proxies.
Override the clock in tests
For now we have an injectable clock in our business logic but how to override it in tests to ensure it is settable? There are multiple options with CDI but the most direct is to specialize the producer to replace it by our custom clock:
@Specializes // <1>
@ApplicationScoped
public class FakeClockProducer extends ClockFactory { // <2>
// if you use a custom qualifier @MyQualifier
@Produces
@Override // <3>
public CustomClock clock() { // <4>
final Clock defaultImpl = super.clock(); // <5>
return new CustomClock() {
@Override
public ZoneId getZone() {
return defaultImpl.getZone();
}
@Override
public Clock withZone(final ZoneId zone) {
return defaultImpl.withZone(zone);
}
@Override
public Instant instant() { // <6>
if (super.instant() != null) {
return super.instant();
}
return defaultImpl.instant();
}
};
}
}
- We notify CDI to replace the default application bean by this subclass,
- We really subclass the bean at java level (otherwise CDI deployment will fail),
- We override our producer but keep @Produces on the mehod (otherwise producer is lost),
- We replace the Clock type by our CustomClock type as returned type to ensure a custom clock can be injected in tests by CDI (to have the setter),
- We get the default impl,
- We fully delegate to the default implementation except if a test set an explicit instance.
Now we can write our CDI test this way:
@Cdi // <1>
public class MyTest {
@Inject
private CustomClock clock; // <2>
@AfterEach // <3>
void resetClock() {
clock.setInstance(null); // back to default
}
@Test
void doTest() { // <4>
clock.setInstant(Instant.now());
doSomethingInCurrentWindow(clock);
clock.setInstant(clock.instant().plusMillis(windowDurationMs));
doSomethingInNextWindow(clock);
}
}
- We enable CDI classpath deployment (this is using openwebbeans-junit5 here but this is actually any testing integration supporting CDI deployment and injections, even Arquillian if you put the test clock class in the Archive),
- We inject our produced clock in the test,
- We don't forget to reset th clock after tests to avoid to have a test failling cause of a previous test,
- We use the clock in all tests needing it.
If you run this test it will likely not work. The reason is that the custom clock is stateful, it stores the instance to use as state. Therefore all injections must share the same instance to ensure it works. To solve that we have two main options:
- Make the test clock producer @ApplicationScoped
- Store the custom clock instance in the enclosing class and implement the producer just as "return instance"
The choice between these two options depends mainly if having a proxy on the Clock instance an issue or not for you. If not just go with 1 which is the cleanest:
?@Specializes
@ApplicationScoped
public class FakeClockProducer extends ClockFactory {
@Produces
@ApplicationScoped // here is the magic
@Override
public CustomClock clock() {
// same as before
}
}
And here we are, we can now test year based algorithm in milliseconds and without any flakiness :).
From the same author:
In the same category: