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! 👍
Top comments (1)
I like it! Thanks for the suggestion 👍