Learn more about Velocity Partners offshore software development company.
Time-dependent unit tests are always tricky. The generally accepted way of doing them has been to wrap all calls to get the system time in a Clock object. In Java 7 and below, it was the coder’s responsibility to create Clock objects. Beginning with Java 8, however, introduced the object, allowing you to inject a real or fake clock into a class for unit testing.
I recently came across a piece of legacy code manipulating time and date, and the original author hadn’t wrapped time calls. Instead, the code contained the following calls in several places:
//Get an instance of Calendar using the static method getInstance() Calendar cal = Calendar.getInstance(); //Manipulating calendar to get a specific date cal.add(Calendar.DATE, 20); //Get the result as a Date object Date finalDate = cal.getTime();
You could also find things like this:
//Get the current date: Date now = new Date();
This is a common pattern with legacy apps that involve time. To test this kind of code, you can use tools like and mock static calls to return mocks of Calendar, which in turn returns mocked and fixed date objects when you call cal.getTime().
But then you won’t be testing date operations, such as adding a day to a date. In the second example, which gets the current time, testing is problematic: This class needs to be refactored in order to make it testable.
Joda-Time
Enter , a common library for handling dates and times in Java that has become the de facto replacement for the Java date and time classes. It contains a powerful utility class that , even if you haven’t used it from the beginning of a project. All objects in the library that get the current time are affected by the time you set with this class. Here’s how we can use it in a unit test setup method:
@Before public void setup(){ //Joda-Time DateTime object receives year, month, day, hour, min, secs as params //Here we set a date of Jan 01 of 2016 at 09:00:00am DateTime fixedDateTime = new DateTime(2016, 1, 1, 9, 0, 0); //Set the fixed date time using the utility class DateTimeUtils.setCurrentMillisFixed(fixedDateTime.getMillis()); } @After public void teardown(){ //Resets the current time to return the system time. DateTimeUtils.setCurrentMillisSystem(); }
This code can set a fixed time before each test and reset to system time once the test concludes. Joda-Time also provides a way to convert the objects of the library to the original Java objects, allowing you to amend legacy code to respect this rule. (Read more about this interoperability ). We’ll take advantage of this and make a small change to our legacy app as follows:
//To get a Calendar instance: Calendar cal = new DateTime().toCalendar(Locale.getDefault()); //Manipulating calendar to get a specific date cal.add(Calendar.DATE, 20); //Get the result as a Date object Date finalDate = cal.getTime();
//Get the current date: Date now = new DateTime().toDate();
As you can see, we are using the DateTime
object in the Joda-Time library as a proxy to create the JDK’s Calendar and Date object. You don’t need to change anything else in your legacy code; it will work as it previously did. The difference now is that when you write a unit test for this code, you get a deterministic result because you fixed the system date to a value that you can use as a base to write your assertions. In the case of the example above, you can assert that the resulting date object is 20 days after Jan 01 of 2016 at 9 am.
//I'm expecting to get a date 20 days later DateTime expectedDate = new DateTime(2016, 1, 21, 9, 0, 0); assertThat(myTestSubject.getExpectedDate(), is(expectedDate.toDate()));
Wrapping Up
This is a very cool trick that allows you to test time on legacy apps and avoid having to resort to complex mocking of the Java date time objects. Ideally, if you have enough time for refactoring, you should move forward and refactor the whole class to use Joda-Time objects, extracting a Clock object to get the current system time. But when time is at a premium, this can help you cover business logic with tests, while having minimal impact on your legacy app. I even wrapped this in a to make it easier to reuse on other tests.
Happy testing!