I really liked the Spring Boot’s concept, since I first saw it. The only thing, I felt, it was missing was a better support for testing in general.

The problem

It all started, when I wanted to have a way to test 'current date' logic in my application. It was supposed to be a reusable, easy-to-use feature (via an annotation) in a custom Spring Boot Starter. The starter is based on Java 8, hence JSR-310 Date / Time API is a natural pick. Current date is only one of several things I want to make "mockable" in integration tests. There are other areas of functionality that are good candidates for mocking out. Keeping that in mind, I will use ZonedDateTime class as a mocking example across the article.

Serving current date in JSR-310

Java 8 comes with new, redesigned API for handling Date and Time. It is designed after Joda-Time, but with many improvements and changes. Here is a nice StackOverflow post pointing out most important ones. One of nice features Joda-Time has, is DateTimeUtils.setCurrentMillisFixed(long fixedMillis). With that method I was able to set fixed current time easily and reset to system time by invoking DateTimeUtils.setCurrentMillisSystem() after test execution. However, it did not make through to Java 8 API. Probably, due to the fact, that Joda-Time implementation of the functionality was kind of hackish. Shared, static variable is used there for keeping MillisProvider instance.

How to achieve the same with JSR-310? Most easy and basic way is to invoke ZonedDateTime.now(). However, it would be bad from testing perspective. How would you test that? There are two more overloaded now methods and we are mostly interested in now(Clock clock). It’s Javadoc confirms that we are in the right place:

Obtains the current date-time from the specified clock.

This will query the specified clock to obtain the current date-time.
The zone and offset will be set based on the time-zone in the clock.

Using this method allows the use of an alternate clock for testing.
The alternate clock may be introduced using dependency injection.

@param clock the clock to use, not null
@return the current date-time, not null

— ZonedDateTime.now(Clock clock) Javadoc

Ok, so now we need Clock instance to pass in. How to get it? Second part of the Javadoc highlighted text has the answer. Treat it as a normal dependency and use dependency injection for that. Hence, the easiest way is to declare a Clock bean in your application:

Specyfing Clock bean
@Bean
public Clock clock() {
    return Clock.systemDefaultZone();
}

and use it as a regular dependency.

Before Spring Boot 1.4.0.M2

It’s time for the main course. How we are going to test current date backed up with injected Clock?

The simplest solution I thought about was to use @Primary:

Specifying primary bean
@Bean
@Primary
public Clock clock() {
    return Clock.fixed(Instant.parse("2010-01-10T10:00:00Z"), ZoneId.of("UTC"));
}

Primary beans are always prefered and picked in a situation where 2 or more beans of the same type are found. It works just fine for a single test case, but if you wanted to reuse it among your test classes, you would need to copy this definition over and over. One can extract it to a super class and use inheritance. I find such solution a design smell. If there are more candidates to mock, several artificial classes has to be created along with an inheritance tree, which would obscure the test code.

As stated before, the solution is going to be a part of a custom Spring Boot starter. Because of that, I was determined to make Clock mocking mechanism annotation based. My first idea was to use @ContextConfiguration in my custom annotations, which would call configuration of mocked Clock with @Primary. After few attempts my lack of Spring knowledge came out. Fortunately, Sam Brannen answered my question and explained it nicely. Next approach was to incorporate Spring profiles. In my test starter I have added profile specific configuration with @Primary mocked out bean dependencies.

Profile specific auto-configuration
@Profile("fixedClock")
@Configuration
public class FixedClockAutoConfiguration {
}

To use them I had to run my integration tests with corresponding profiles via @ActiveProfiles. There were two major drawbacks I didn’t like with the solution:

  • Active profiles names had to be provided explicitly, that would mean remembering all available profile names.

  • Mocked clock has some default value. I wanted to provide an option to override this value. That was possible via @TestPropertySource, but again, property name had to be remembered.

Was there anything else I could do, but I’m just not aware of? Eventually, Phil Webb clarified that Spring Boot 1.3 does not have the tools I’m looking for. With the bad news he brought, he brought also hope…​ Spring Boot 1.4 is supposed to bring heavy testing enhancements. Simplified annotation naming, focused testing (on JSON, MVC or JPA slices) and desired mocking support.

I had to wait…​ and for the time being, I did a slight improvement over my profiles. I created a simple implementation of ActiveProfilesResolver, which enables picking proper profile via an annotation:

Example active profile resolver
public class TestProfilesResolver implements ActiveProfilesResolver {

    ImmutableMap<Class<? extends Annotation>, String> PROFILES = ImmutableMap.<Class<? extends Annotation>, String>builder()
            .put(Wiremock.class, "wireMock")
            .put(FixedClock.class, "fixedClock")
            .build();

    @Override
    public String[] resolve(Class<?> testClass) {
        List<String> profiles = Lists.newArrayList();
        Arrays.stream(testClass.getAnnotations()).forEach(annotation -> {
            Class<? extends Annotation> annotationType = annotation.annotationType();
            if (PROFILES.containsKey(annotationType)) {
                profiles.add(PROFILES.get(annotationType));
            }
        });
        return profiles.toArray(new String[profiles.size()]);
    }
}

To use it, annotate your integration test with @ActiveProfiles(resolver = TestProfilesResolver.class).

It worked, but it had to be a part of the application and not a part of the starters I was preparing. Reason for that are mock candidates - located in many different starters. Without hardcoding all of them in a single one, it wouldn’t be particularly easy to accomplish.

Do it like a boss

Ok, so all the goodies are in place - Spring Boot 1.4.0.M2 is out for some time now. Mockito support included. All tutorials and documentation shows, however, only the basic usage of @MockBean annotation. Which is specifying the annotation on a class field and using Mockito methods directly in the test class:

Example @MockBean usage
@RunWith(SpringRunner.class)
@SpringBootTest
public class MockBeanIntegrationTest {

    @MockBean
    private SomeService someService;

    @Before
    public void setupMock() {
        when(someService.getResult())
            .thenReturn("success");
    }
}

With this in place, SomeService dependency is mocked out and set up to return "success" String anytime getResult() is invoked. Mocks are reset after each test method by default. There is also analogical support for spying beans via @SpyBean annotation. It all works great, but there is more to that!

@MockBean, implemented as meta-annotation, combined with TestExecutionListener is something I was looking for the whole time. The idea is simple - create annotation that would indicate mocking particular dependency and handle mocking internals in the execution listener:

First thing we can do is define our annotation.

@Documented
@Inherited
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@MockBean(value = Clock.class, reset = MockReset.NONE) (1)
public @interface FixedClock {
    String value() default "2010-01-10T10:00:00Z";
}
1 This is the only interesting part here. Anytime you annotate your test with @FixedClock, it substitutes application context’s Clock-type bean with a mock. We are disabling mock reset deliberately - it will be handled by the TestExecutionListener.

TestExecutionListener is a feature in spring-test component, that allows to plug 'Spring context'-aware custom code in JUnit test lifecycle phases. As explained in documentation, there are couple of default listeners registered. You can use your own by using @TestExecutionListeners annotation on a given test, but a better way is to register it automatically via META-INF/spring.factories properties (under org.springframework.test.context.TestExecutionListener key). If order of your listeners is important you can easily assign the order value by implementing Ordered or by annotating your listener with @Order.

TestExecutionListener interface has couple of methods:

public interface TestExecutionListener {
    void beforeTestClass(TestContext testContext) throws Exception;
    void prepareTestInstance(TestContext testContext) throws Exception;
    void beforeTestMethod(TestContext testContext) throws Exception;
    void afterTestMethod(TestContext testContext) throws Exception;
    void afterTestClass(TestContext testContext) throws Exception;
}

Let’s see how our FixedClockListener can implement these methods:

public class FixedClockListener extends AbstractTestExecutionListener {

    @Override
    public void beforeTestClass(TestContext testContext) throws Exception {
        FixedClock classFixedClock = AnnotationUtils.findAnnotation(testContext.getTestClass(), FixedClock.class); (1)
        if (classFixedClock == null) {
            return;
        }
        mockClock(testContext, classFixedClock); (2)
    }

    @Override
    public void beforeTestMethod(TestContext testContext) throws Exception {
        FixedClock methodFixedClock = AnnotationUtils.findAnnotation(testContext.getTestMethod(), FixedClock.class); (6)
        if (methodFixedClock == null) {
            return;
        }
        verifyClassAnnotation(testContext); (7)
        mockClock(testContext, methodFixedClock);
    }

    @Override
    public void afterTestMethod(TestContext testContext) throws Exception {
        FixedClock methodFixedClock = AnnotationUtils.findAnnotation(testContext.getTestMethod(), FixedClock.class);
        if (methodFixedClock == null) {
            return;
        }
        verifyClassAnnotation(testContext);

        FixedClock classFixedClock = AnnotationUtils.findAnnotation(testContext.getTestClass(), FixedClock.class); (8)
        mockClock(testContext, classFixedClock);
    }

    @Override
    public void afterTestClass(TestContext testContext) throws Exception {
        FixedClock annotation = AnnotationUtils.findAnnotation(testContext.getTestClass(), FixedClock.class);
        if (annotation == null) {
            return;
        }
        reset(testContext.getApplicationContext().getBean(Clock.class)); (9)
    }

    private void verifyClassAnnotation(TestContext testContext) {
        FixedClock classAnnotation = AnnotationUtils.findAnnotation(testContext.getTestClass(), FixedClock.class);
        if (classAnnotation == null) {
            throw new IllegalStateException("@FixedClock class level annotation is missing.");
        }
    }

    private void mockClock(TestContext testContext, FixedClock fixedClock) {
        Instant instant = Instant.parse(fixedClock.value()); (3)
        Clock mockedClock = testContext.getApplicationContext().getBean(Clock.class); (4)
        when(mockedClock.instant()).thenReturn(instant); (5)
        when(mockedClock.getZone()).thenReturn(TimeZone.getDefault().toZoneId());
    }
}
1 Simple check if a test class is annotated with our annotation. If not - skip further processing. Prefer AnnotationUtils.findAnnotation() over simple testClass().getAnnotations() if you want to allow your annotation to be a part of different, composed annotation.
2 Extracted method for setting up our mock.
3 Retrieved Instant object out of our annotation. Will be used as a mock stub value.
4 Clock bean is mocked by @FixedClock annotation and here we are fetching the mock from the application context, so we can provide mocking stubs on the mock instance.
5 Here we provide the stubs. As they has to be declared on method calls, we cannot simply just declare Clock.fixed() here. Fortunately, there are only two methods to stub: instant(), and getZone().
6 With test execution listener approach, we can handle overriden fixed Clock values per test method. Here again, a simple check if the method is in fact annotated. If not - skip processing.
7 For test methods, we need to implement additional verification step. We need to check if the test class was annotated as well. @MockBean will work only if test class was marked with it.
8 After test method execution, revert mock stub to what was specified globally (per test class).
9 Eventually, when all tests ran, reset the mock.

With all we did so far, we can easily test code below:

@RestController("/api/time")
@AllArgsConstructor
public class TimeEndpoint {
    private final Clock clock;

    @GetMapping
    public ZonedDateTime getTime() {
        return ZonedDateTime.now(clock);
    }
}

and provide fixed current time in integration test:

@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@FixedClock
public class FixedClockTest {

    @LocalServerPort
    int port;

    @Before
    public void setUp() {
        RestAssured.port = this.port;
    }

    @Test
    public void testClock() throws Exception {
        get("/api/time")
        .then()
            .body(containsString("2010-01-10T10:00:00Z")); // default @FixedClock value

    }

    @Test
    @FixedClock("2011-11-11T11:00:00Z")
    public void testClockOverridden() throws Exception {
        get("/api/time")
        .then()
            .body(containsString("2011-11-11T11:00:00Z"));

    }
}

Summary

At last, it seems, that Spring Boot with 1.4.0.M2 release, received last, missing piece in its testing toolbox.
Proposed solution will not only suit in a custom starter (but it’s a great fit). You can implement similar solution in the actual application and you don’t have to limit yourself to mocking current date. This approach let you mock anything you find appropriate, keeping your tests clean.