Often we need to mock time in when testing logic that is dependent on it. LocalDate, LocalTime, LocalDateTime and ZonedDateTime form the java.time.* package have a method now that accepts an optional clock. By creating a bean that returns a Clock and inject it into our service using dependency injection we can mock that bean in the tests.
Create a configuration class and with a @Bean returning a Clock with the systems current time and zone.
@Configuration
public class ClockConfig {
@Bean
Clock clock() {
return Clock.systemDefaultZone();
}
}
Inject the Clock into your logic class using dependency injection. Spring will find the bean you created above.
In any calls to LocalDate.now(clock)
, LocalTime.now(clock)
, LocalDateTime.now(clock)
or ZonedDateTime.now(clock)
pass in the injected clock.
@Service annotation tells spring to create a singleton bean of this class on the class path.
// Annotate your @Service, @RestController or @Component
@Service
public record MyDateTimeService(Clock clock) { // inject the Clock
public LocalDateTime currentTime() {
// Pass the injected clock into the now call.
return LocalDateTime.now(clock);
}
}
In the SpringBootTest create a @MockBean Clock and set its time and zone with Mockito. Then call the service you wish to test.
@SpringBootTest
class ShouldIDeployServiceUnitTest {
// Inject the service to test
@Autowired MyDateTimeService serviceUnderTest;
// Mock the Clock Bean
@MockBean Clock clock;
@Test
void setTheClockTo2042() {
// Set the Mocked Time
given(clock.instant()).willReturn(Instant.parse("2042-01-01T12:15:00Z"));
given(clock.getZone()).willReturn(ZoneId.of("UCT"));
// Call the service
String actual = serviceUnderTest.currentTime().toString();
assertThat(actual).isEqualTo("2042-01-01T12:15");
}
}
The same strategy also works for MockMvc integration tests with a standard rest controller.
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.MOCK)
@AutoConfigureMockMvc()
class ShouldIDeployIntegrationTest {
@Autowired
private MockMvc mockMvc;
@MockBean Clock clock;
@Test
void setTheClockTo1999() {
given(clock.instant()).willReturn(Instant.parse("1999-09-19T19:19:00Z"));
given(clock.getZone()).willReturn(ZoneId.of("UCT"));
System.out.println(LocalDateTime.now(clock));
mockMvc.perform(MockMvcRequestBuilders.get("/now"))
.andExpect(MockMvcResultMatchers.status().isOk())
.andExpect(MockMvcResultMatchers.content().string("\"1999-09-19T19:19:00\""));
}
}
Good Luck out there.