Testing date based business logic is hard, since Spring is now based on Java 8 its possible to use java.time.Clock and mutable clock from Three Ten Extra. It would be useful to include such a mutable clock in spring test context framework along with an injectable Clock that returns the default system clock. Below is code that implement this.
/**
* A factory for obtaining an instance fo the java.util.clock that will be injected into by spring
* into any components that require a clock to know the current time.
*/
public interface ClockProvider {
Clock clock();
}
/** Default Clock provider for the application. It returns the same clock used by Instant.now() */
@Component
class DefaultClockProvider implements ClockProvider {
@Override
public Clock clock() {
return Clock.systemUTC();
}
}
notice that I have to use @Primary and @TestFixtureComponent on my test fixture implementation that provides a jsr 310 extras mutable clock.
@Primary
@TestFixtureComponent
class TestClockProvider implements ClockProvider {
private final Logger logger = LoggerFactory.getLogger(TestClockProvider.class);
@PostConstruct
void init() {
logger.warn("** ++ TestClockProvider with a Mutable Clock In Use ++ **");
}
@Override
public Clock clock() {
return MutableClock.of(Instant.now(), ZoneOffset.UTC);
}
}
There is also a @Configuration class to create a Clock Bean so that my code can just inject a java.util.Clock where it needs to know the current time. This class is in src/main/java
@Configuration
class ClockConfig {
@Bean
Clock clock(ClockProvider clockProvider) {
return clockProvider.clock();
}
}
TimeMachine class below can be autowired into test classes and used to move the clock forwards, backwards ... etc.
@TestFixtureComponent
@ConditionalOnBean(TestClockProvider.class)
public class TimeMachine {
private final MutableClock clock;
public TimeMachine(Clock clock) {
this.clock = (MutableClock) clock;
}
/**
* Advanced the clock used by the application by a certain number of seconds.
*
* @param seconds the number of seconds to advance the clock by
*/
public void advanceSeconds(long seconds) {
this.clock.add(Duration.ofSeconds(seconds));
}
}
A lot of business logic does date and time comparisons injection of a clock is the recommended way to do this in Java 8 and above. Adding Spring support for virtual clock is quite useful.
Comment From: sbrannen
Why do you need the ClockProvider API?
Why not just register a Clock bean directly in the ApplicationContext and simply override it in test config with a mutable variant?
Comment From: asaikali
I can't remember why I needed the ClockProvider I will try to remove it and see if I can remember the real reason why. When I was writing this code, I was also learning how to use gradle, junit5 features, figuring out the design of what I am building ... etc so could be a good refactoring.
Below is my guess as to why I did it that way.
I started out with beans defined via @Bean on @Configuration then I decided that I really don't like writing useless code. So I switched to @Component scanning with constructor injection. Some of my src/test classes contain samples using @SpringBootApplication applications that explore how a user would use a collection of classes outside of a test case scenario, so I wanted to see in the log warnings when I was using any kind of replacement test fixtures, in case my CI failed and i needed to investigate, @PostConstruct seemed like a good way to do things.
@PostConstruct
void init() {
logger.warn("** ++ TestClockProvider with a Mutable Clock In Use ++ **");
}
Comment From: sbrannen
Team Decision: the core Spring Framework team feels that it is best for applications to simply declare a Clock bean in the ApplicationContext and then override that bean in testing scenarios -- for example with bean definition profiles and/or the use of @Primary.
In light of that, I am closing this issue.
As a side note, the logging you have introduced could also be implemented directly in the @Bean method that configures the mutable Clock implementation or, if you prefer, in a @PostConstruct method in the @Configuration class that declares that @Bean method.