Unit testing time driven methods

ยท

3 min read

Unit testing time driven methods

Hi! This is my first post on a series of quick tips to improve your tests in Java, I hope you will learn something today ๐Ÿ‘

Have you ever used Instant.now(), LocalDateTime.now() or ZonedDateTime.now() in your code, only to find it difficult to test later on ?

Let's take an example :

public class Hohoho {

    public String isItChristmasYet() {
        LocalDate now = LocalDate.now();

        if (now.getMonth() == Month.DECEMBER) {
            if (now.getDayOfMonth() == 25) {
                return "Yes! Merry Christmas! ๐ŸŽ…";
            }
            else if (now.getDayOfMonth() == 24) {
                return "Almost there! ๐Ÿ˜";
            }
        }
        return "Not yet ๐Ÿ˜”";
    }
}

How would you test this method?

You could pass the LocalDate as an argument of the method but that would change the semantic of the method.

You could also use some libs like Powermockito to mock the static method of LocalDate.now() but this would also affect other tests.

Here is a tip : use a java.time.Clock!

Every time you need to use a *.now() method, pass a Clock object as a parameter. You can set the clock to be the system default in you constructor, and add a setter method to override it during tests.

Let's apply this in our example :

public class Hohoho {

    private Clock clock;

    public Hohoho() {
        // Set a normal value for the clock, here UTC
        this.clock = Clock.systemUTC();
    }

    // For testing purpose : override the clock
    public void setClock(Clock clock) {
        this.clock = clock;
    }

    public String isItChristmasYet() {
        LocalDate now = LocalDate.now(clock); // <- use the clock

        if (now.getMonth() == Month.DECEMBER) {
            if (now.getDayOfMonth() == 25) {
                return "Yes! Merry Christmas! ๐ŸŽ…";
            }
            else if (now.getDayOfMonth() == 24) {
                return "Almost there! ๐Ÿ˜";
            }
        }
        return "Not yet ๐Ÿ˜”";
    }
}

If you want to avoid the setter, you could inject the Clock through dependency injection. I personally don't mind having a setter in this case.

Now let's look how this will help our tests :

class HohohoTest {

    private Hohoho underTest = new Hohoho();

    @DisplayName("Should respond 'Not Yet' in February")
    @Test
    void februaryTest() {
        // Fix the clock to my birthday
        Clock clock = Clock.fixed(Instant.parse("2022-02-21T12:00:00Z"), ZoneId.of("UTC"));
        underTest.setClock(clock);

        String actual = underTest.isItChristmasYet();

        assertThat(actual).isEqualTo("Not yet ๐Ÿ˜”");
    }

    @DisplayName("Should respond 'Almost there' the 24th of December")
    @Test
    void december24thTest() {
        // Fix the clock to christmas eve
        Clock clock = Clock.fixed(Instant.parse("2022-12-24T12:00:00Z"), ZoneId.of("UTC"));
        underTest.setClock(clock);

        String actual = underTest.isItChristmasYet();

        assertThat(actual).isEqualTo("Almost there! ๐Ÿ˜");
    }

    @DisplayName("Should respond 'Yes' on Christmas")
    @Test
    void christmasTest() {
        // Fix the clock to christmas day
        Clock clock = Clock.fixed(Instant.parse("2022-12-25T12:00:00Z"), ZoneId.of("UTC"));
        underTest.setClock(clock);

        String actual = underTest.isItChristmasYet();

        assertThat(actual).isEqualTo("Yes! Merry Christmas! ๐ŸŽ…");
    }

}

By fixing the Clock object to a specific instant in time, every method now() that use the clock will return exactly the same result.

This means your test will always be consistent, using the same values every time, no mater when they are run : be it on new year's eve, 28th of February or while switching from winter time to summer time!

Do you have another approach for testing time sensitive methods? Let me know in the comments! ๐Ÿ‘

ย