Jul 12, 2008

Evil 'System.currentTimeMillis()' and 'new Date()'

If you use TDD like me or just write unit tests for your code then you have already met issues with testing dates. There are two common ways to retrieve current time in Java: 'System.currentTimeMillis()' and 'new Date()'. Both of them make your code untestable. To illustrate this effect imagine that you test service for user registration and user record in the database contains registration date. When you create database record you put current date as registration date. But how to check that record is created correctly? Yes, perform assert operation. But what date should you place in your expected object? There is no easy answer. Of course you can check that registration date is in the interval of date when test was started and date when assert is performed. But then you will introduce additional variables and still can't assert full domain object using for equals. Similar issues exist for asserting method parameters and other parts of your testing. So I have created very useful utility class:

public class Clock {
private static long time;

private Clock() {
}

public static Date getCurrentDate() {
return new Date(getTimeMillis());
}

public static long getTimeMillis() {
return (time == 0 ? System.currentTimeMillis() : time);
}

public static void setTimeMillis(long millis) {
Clock.time = millis;
}

public static void resetTime() {
Clock.time = 0;
}
}

This class encapsulate all work with date creation and can be easy stubbed by setting internal time for particular usage. After test invocation it should be reset to disable stub behavior (DON'T FORGET TO DO IT). For some projects I extend this class with methods to retrieve shifted data to represent data in the future or in the past. If you need more complex logic (for example you will invoke date creation some times in the tested logic) then you may change 'time' static variable with 'DateTimeStrategy' class instance:

public interface DateTimeStrategy {
long getTimeMillis();
}

Then you will have more control on the data creation in your tests by implementing your own strategies. This approach not only helps me with testing but also encapsulate logic for dates creation in one place but makes my code clearer. I hate Java native implementation of work with dates and hope that in future it will be changed with something like Joda Time. I hope this approach will make your tests better. Develop with pleasure!

No comments: