Time dependent unit tests

I need to test a function that whose result will depend on current time (using Joda time's isBeforeNow(), it so happens).

public boolean isAvailable() {
return (this.someDate.isBeforeNow());
}

Is it possible to stub/mock out the system time with (using Mockito, for example) so that I can reliably test the function?

49848 次浏览

The best way (IMO) of making your code testable is to extract the dependency of "what's the current time" into its own interface, with an implementation which uses the current system time (used normally) and an implementation which lets you set the time, advance it as you want etc.

I've used this approach in various situations, and it's worked well. It's easy to set up - just create an interface (e.g. Clock) which has a single method to give you the current instant in whatever format you want (e.g. using Joda Time, or possibly a Date).

Joda time supports setting a "fake" current time through the setCurrentMillisFixed and setCurrentMillisOffset methods of the DateTimeUtils class.

See https://www.joda.org/joda-time/apidocs/org/joda/time/DateTimeUtils.html

I use an approach similar to Jon's, but instead of creating a specialized interface just for the current time (say, Clock), I usually create a special testing interface (say, MockupFactory). I put there all the methods that I need to test the code. For example, in one of my projects I have four methods there:

  • one that returns a mock-up database client;
  • one that creates a mock-up notifier object that notifies the code about changes in the database;
  • one that creates a mock-up java.util.Timer that runs the tasks when I want it to;
  • one that returns the current time.

The class being tested has a constructor that accepts this interface among other arguments. The one without this argument just creates a default instance of this interface that works "in real life". Both the interface and the constructor are package private, so the testing API doesn't leak outside of the package.

If I need more imitated objects, I just add a method to that interface and implement it in both testing and real implementations.

This way I design code suitable for testing in the first place without imposing too much on the code itself. In fact, the code becomes even cleaner this way since much factory code is gathered in one place. For example, if I need to switch to another database client implementation in real code, I only have to modify just one line instead of searching around for references to the constructor.

Of course, just as in the case with Jon's approach, it won't work with 3rd party code that you are unable or not allowed to modify.

Java 8 introduced the abstract class java.time.Clock which allows you to have an alternative implementation for testing. This is exactly what Jon suggested in his answer back then.

To add to Jon Skeet's answer, Joda Time already contains a current time interface: DateTimeUtils.MillisProvider

For example:

import org.joda.time.DateTime;
import org.joda.time.DateTimeUtils.MillisProvider;


public class Check {
private final MillisProvider millisProvider;
private final DateTime someDate;


public Check(MillisProvider millisProvider, DateTime someDate) {
this.millisProvider = millisProvider;
this.someDate = someDate;
}


public boolean isAvailable() {
long now = millisProvider.getMillis();
return (someDate.isBefore(now));
}
}

Mock the time in a unit test (using Mockito but you could implement your own class MillisProviderMock):

DateTime fakeNow = new DateTime(2016, DateTimeConstants.MARCH, 28, 9, 10);
MillisProvider mockMillisProvider = mock(MillisProvider.class);
when(mockMillisProvider.getMillis()).thenReturn(fakeNow.getMillis());


Check check = new Check(mockMillisProvider, someDate);

Use the current time in production (DateTimeUtils.SYSTEM_MILLIS_PROVIDER was added to Joda Time in 2.9.3):

Check check = new Check(DateTimeUtils.SYSTEM_MILLIS_PROVIDER, someDate);