3 tips for using Spring Boot test slices

Introduction

Spring Boot test slices are a great way to test your services with Spring Boot beans and configuration. They will load only a necessary part of framework for you and thus making integration tests:

  • faster,
  • easier to reason about,
  • and less fragile (as they will not break on changes in unrelated configuration / beans).

Some time back I've written an article about making DataJpaTest to catch more bugs but let's have a look now at some general tips on using all types of slices:

Tip 1: How to resolve dependencies of a tested service

Many times a test with a slice will need additional dependencies. There are a few ways how to load them to the test context:

  • @MockBean - creates a mockito instance for annotated field. Do not confuse this annotation with @Mock from Mockito library.
  • @Import - loads a service/configuration into the test context.
  • @AutoConfigure* annotations - autoconfigures more beans from SpringBoot application; for example @AutoConfigureJson will configure Jackson's ObjectMapper. - You should have only one slice annotation on a test class but you can add more autoconfigurations if necessary.

Let's have a look at an example with a CreateUserService depending on JPA repositories, Jackson ObjectMapper and some custom classes for generating passwords / sending emails:

@DataJpaTest
@AutoConfigureJson
@Import({
    CreateUserService.class,
    RandomPasswordGenerator.class
})
class CreateUserServiceTest {
    @MockBean
    private EmailSender emailSender;

    @Autowired
    private CreateUserService createUserService;

    // ...
}

Tip 2: Make sure SpringBootApplication does not have unnecessary configuration

Your main application class should ideally look like this:

@SpringBootApplication(
    exclude = {/* any autoconfiguration you want to disable */}
)
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

Any additional configuration would be loaded by all slices too and so it should go to separate code>@Configuration classes if possible. (See docs for configuration and slices.)

Be especially aware of code>@ComponentScan annotation - do not forget to put a TypeExcludeFilter into it otherwise your slices will start loading all configurations and components from specified packages. The TypeExcludeFilter is one of classes responsible for making slices work.

Tip 3: You can extend existing slices

Do you need additional beans for every single WebMvcTest (or any other test slice)? Simply extend the existing slice and import/mock your dependencies:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@WebMvcTest
@Import(MandatoryDependency.class)
public @interface CustomWebMvcTest {
    @AliasFor(annotation = WebMvcTest.class, attribute = "controllers")
    Class<?>[] controllers() default {};
}

Notice that you can also "forward" fields of the new slice to the original slice using code>@AliasFor annotation.

Final thoughts

I guess this goes without saying - but checking documentation, or implementation of slices can show you even more possibilities. For example DataJpaTest automatically configures TestEntityManager, WebMvcTest can load only one controller using controllers attribute, JsonTest provides JacksonTester class for easy asserting of JSON content and so on...

There could be a lot written about how to use any particular slice but hopefully these simple tips will help you to use them better.