If you're a seasoned unit tester, you've learned to take note when you see any code working with time,*concurrency*, random, persistence and disc I/O.
The reason for this is that tests can be very brittle and sometimes down-right impossible to test properly. This post will show how to abstract out "time" by injecting a replacement for it in the consumer. This post will be using Spring 3 as the Dependency Injection container, though Guice, other DI containers or constructor/setters on POJOs would work as well. I will also ignore Locales since the focus is on the injection of the DateTime, not DateTime itself.
You've been handed a piece of code to unit test (or you are creating one and this is your first stab at it). Our first piece of code, only one class: (This class is a Spring 3.1 controller and the purpose is to return back the current time as a String)
@Controller
@RequestMapping(value = "/time")
@VisibleForTesting
class TimeController {
@RequestMapping(value = "/current", method = RequestMethod.GET)
@ResponseBody
public String showCurrentTime() {
// BAD!!! Can't test
DateTime dateTime = new DateTime();
return DateTimeFormat.forPattern("hh:mm").print(dateTime);
}
}
Take note that the class does a "new DateTime()" in the class. Here is the corresponding test class:
What happens when we run the test? How about assuming we have a very slow machine. You could (and most likely will) end up with your comparison DateTime to be different than the returned DateTime. This is a problem!
First thing to do is to remove the dependency, but how are we going to do this? If we make the DateTime a field on the class, we will still have the same problem. Introduce Google Guava's Supplier interface.
The Supplier interface only has one method, "get()" which will return an instance of whatever the supplier is setup for. An example, the supplier will return a user's first name if they have logged in, and a default one if they have not:
public class FirstNameSupplier implements Supplier<String> {
private String value;
private static final String DEFAULT_NAME = "GUEST";
public FirstNameSupplier() {
// Just believe that this goes and gets a User from somewhere
String firstName = UserUtilities.getUser().getFirstName();
// more Guava
if(isNullOrEmpty(firstName)) {
value = DEFAULT_NAME;
} else {
value = firstName;
}
}
@Override
public String get() {
return value;
}
}
To your implementing method, you don't care what the first name is, only that you get one.
Let's move on. For a much more real example of using a Supplier (and the point of this post) let's implement a DateTime supplier to give us back the current DateTime. While we're at it, let's also create an interface so that we can create mock implementations for testing:
public interface DateTimeSupplier extends Supplier<DateTime> {
DateTime get();
}
and an implementation:
public class DateTimeUTCSupplier implements DateTimeSupplier {
@Override
public DateTime get() {
return new DateTime(DateTimeZone.UTC);
}
}
Now we can take the DateTimeUTCSupplier and inject that into our code that needs the current*DateTime* as the DateTimeSupplier interface:
@Controller
@RequestMapping(value = "/time")
@VisibleForTesting
class TimeController {
@Autowired
@VisibleForTesting
// Injected DateTimeSupplier
DateTimeSupplier dateTime;
@RequestMapping(value = "/current", method = RequestMethod.GET)
@ResponseBody
public String showCurrentTime() {
return DateTimeFormat.forPattern("hh:mm").print(dateTime.get());
}
}
In order to test this, we'll need to create a MockDateTimeSupplier and have a controller to pass in the specific instance we want to return:
public class MockDateTimeSupplier implements DateTimeSupplier {
private final DateTime mockedDateTime;
public MockDateTimeSupplier(DateTime mockedDateTime) {
this.mockedDateTime = mockedDateTime;
}
@Override
public DateTime get() {
return mockedDateTime;
}
}
Notice that the object being saved is passed in via the constructor. This will not get you the current date/time back, but will return back the specific instance you want
and finally our test that exercises (slightly) the TimeController we implemented above:
public class TimeControllerTest {
private final int HOUR_OF_DAY = 12;
private final int MINUTE_OF_DAY = 30;
@Test
public void testShowCurrentTime() throws Exception {
TimeController controller = new TimeController();
// Create the mock DateTimeSupplier with our known DateTime
controller.dateTime = new MockDateTimeSupplier(new DateTime(2012, 1, 1, HOUR_OF_DAY, MINUTE_OF_DAY, 0, 0));
// Call our method
String dateTimeString = controller.showCurrentTime();
// Using hamcrest for easier to read assertions and condition matchers
assertThat(dateTimeString, is(String.format("%d:%d", HOUR_OF_DAY, MINUTE_OF_DAY)));
}
}
This post has shown how to use Google Guava's Supplier interface to abstract out a DateTime object so you can better design your implementations with unit testing in mind! Suppliers are a great way to solve the "just give me something", mind you it's a known type of something.
Good luck!
*From http://www.ensor.cc/2012/01/mocking-with-jodatimes-datetime-and.html