Unit test time based logic

A standard unit testing problem is how to unit test code that has a dependency on dates or times. For example a method that returns a greeting according to the time of day:

public String timeOfDayGreeting() {
    LocalTime now = LocalTime.now();
    if (now.isBefore(LocalTime.NOON)) {
        return "Good morning";
    } else if (now.isBefore(LocalTime.of(18, 00))) {
        return "Good afternoon";
    } else {
        return "Good evening";
    }
}

If we were to call this method from a test fixture (say JUnit), it would return different values depending on when the test was run. This is not ideal. Unit tests should pass or fail consistently.

Here’s a simple solution for testing time based code.

Inject the time

The problem with the above code is the static call to LocalTime.now(). Yes, it is possible to mock static methods. Powermock does this well. But even Powermock makes unit tests a little awkward and hard to read.

A better solution is to inject the time as a class instance variable. This helps because instance variables are usually easier to mock.

We want to inject an object that will provide the current time of day. A Supplier is ideal for this. Supplier is a functional interface that takes no input and returns a result. In this case, the result is the current time of day. The refactored code looks like this:

private Supplier<LocalTime> currentTime = LocalTime::now;

public String timeOfDayGreeting() {
    LocalTime now = currentTime.get();
    if (now.isBefore(LocalTime.NOON)) {
        return "Good morning";
    } else if (now.isBefore(LocalTime.of(18, 00))) {
        return "Good afternoon";
    } else {
        return "Good evening";
    }
}

The currentTime instance variable can be injected but its default value is the LocalTime.now()  static method reference. That is, when you call currentTime.get(), it will return the result of LocalTime.now().

Note that simply setting an instance variable to LocalTime.now() will not work.

LocalTime currentTime = LocalTime.get();

The instance variable would be initialised to the time of day when the object is created and never updated. Instead, we use the Supplier so that we can get the current time whenever we request it.

Inject a mock

Following this refactor, we can now inject the time Supplier in a unit test. I like Mockito’s JUnit Runner for simple dependency injection. It allows us to set up the test like this:

@RunWith(MockitoJUnitRunner.class)
public class DisplaySpannersControllerTest {
    @Mock private Supplier<LocalTime> currentTime;
    @InjectMocks private DisplaySpannersController controller = new DisplaySpannersController();

    @Test
    public void testTimeOfDayGreetingMorning() {

        // Set the current time to 9AM
        when(currentTime.get()).thenReturn(LocalTime.of(9,0));

        // Call the controller
        ModelMap model = new ModelMap();
        controller.displaySpanners(model);

        // Assert that time of day greeting is morning
        String greeting = (String)model.get("greeting");
        assertEquals("Good morning", greeting);
    }

}

Here line 3 creates a mock instance of the LocalTime Supplier. Then line 10 defines the behaviour of the mock. In this case return LocalTime.of(9,0)  (9AM) when the Supplier is asked for the time.

Success! We have successfully decoupled our test from the actual time of day. The testTimeOfDayGreetingMorning() test method will pass no matter what time of day the test is run. It should be clear that this technique can be extended to any class / method where we have date / time dependent logic.

The full working example class can be found in the Spanners project in GitHub. The class under test is DisplaySpannersController and the test class is DisplaySpannersControllerTest.

Leave a Reply

Your email address will not be published. Required fields are marked *