Overview
Getting time under control is often something we overlook until late into a project. There are many ways to address this from ignoring it and fixing data to fit the current time, manually setting the time on the computer upon which we run tests, or somehow get between the JVM and the system time. With Java 8 that became a bit easier. Here’s a way to accomplish time control using Java 8’s Clock, with a Spring application for a (trivial) example.
In Java 8 there are several options. Baeldung has a great article on those options. What follows is taking one of the options from that article all the way into a Spring-based project.
Background
In a system where time is important, and you want to write tests where the actual time used is part of what you are checking, then getting time under control is a system requirement. It’s fair to say that system time is a domain-level concept.
Normally, Time is an external dependency because it is based on the clock in the computer upon which the app is running. Tests attempt to get every dependency under the control of the test. While not always possible, it is an ideal to strive for.
Generally, the more focused the test, the more likely controlling all dependencies is possible. Getting all the dependencies controlled for a micro-test is generally much easier / more possible than for an integration test, and an integration test tends to be easier than an acceptance test.
Java 8
Java 8 added an entirely new date/time implementation based on Joda Time and written by its author. The new and old co-exist, but are fairly disconnected.
What follows are the moving parts to time travel in a spring-based test.
How to get time
For a component that needs time, instead of using Date
, use Clock
.
For example, instead of:
new Date() // harder to control
We use this:
Date.from(systemClock.instant())
// or
Date.from(Instant.now(systemClock))
How to use the clock we define
Consider a Spring component that cares about time:
@Component
public class HelloComponent {
private final Clock systemClock;
public HelloComponent(Clock systemClock) {
this.systemClock = systemClock;
}
public String message() {
return "Hello, it is now: " + Date.from(systemClock.instant());
}
}
Part of Application Configuration
For systemClock to be injected, there needs to be a bean in the Spring configuration. The special sauce is a combination of two annotations:
@Configuration
enhance the Spring context with some bean instances@Bean
Define a Spring bean that can be autowired into a component
This can be on its own class or, in this simple example, placed on the @SpringBootApplication
directly.
@SpringBootApplication
@Configuration
public class TimeApplication {
public static void main(String[] args) {
SpringApplication.run(TimeApplication.class, args);
}
@Bean
public Clock systemClock() {
return Clock.systemDefaultZone();
}
}
Not this Now, That Now
What about testing for a date that is not “now” in the real world?
Override the Clock
If you can get away with using a plain JUnit test, then you can construct
your own Clock
instance, and manually call the constructor of the HelloComponent
.
That’s straightforward and a good option.
If you’re trying to get it to work in a spring context, it requires another moving part. We’ll override the application-generated Clock with a test Clock.
Create a Test Config
public class TestConfig {
@Bean
@Primary
public Clock systemClock() {
return Clock.fixed(
Instant.parse("2031-08-22T10:00:00Z"),
ZoneOffset.UTC);
}
}
Note, the use of @Primary
on this @Bean
method allows this TestConfig to work on its own, or in a larger
initialization context.
In the test that follows, the test limits what is in the Spring context for test execution by listing specific classes that the test-writer knew were needed to get this test to run.
Alternatively, you can specify the application class and get a fully initialized spring context with all the project’s components, controllers, services, etc.
If you fully initialize your application, then in addition to using @Primary
, and @Bean
, such a test
would also need the annotation:
@SpringBootTest(classes = {HelloComponent.class, TestConfig.class})
@TestPropertySource(properties="spring.main.allow-bean-definition-overriding=true")
class HelloComponentTest {
// ...
}
Oh yes, how about a test
@SpringBootTest(classes = {HelloComponent.class, TestConfig.class})
class HelloComponentTest {
@Autowired
HelloComponent hello;
@Test
void messageControlled() {
String message = hello.message();
assertThat(message).contains("2031");
}
}
If you look at the test config, the date is fixed to 2031-08-22, so while the assertion could be more specific, it demonstrates that we are overriding the real time with our desired time.
Unless it is 2031. If you run this test in the year 2031, then the test passing proves nothing. Do you see?
Conclusion
- The most important takeaway is that anything we care about in the application is worth getting under control.
- If your system cares about time, then getting time under control is a good idea because it is part of the domain. That is, it is a business-level concept worthy of explicit representation in the production code.
- This shows one path with Java 8 and Spring. While these details are often of an immediate nature, the previous two bullets are a bit longer-lived.