Spring Boot offers great support for testing different slices (web, database, etc.) of your Java application. This allows you to write tests for specific parts of your application in isolation without bootstrapping the whole Spring Context. Technically this is achieved by creating a Spring Context with only a subset of beans, by applying only specific auto-configurations. Continue reading to get to know the most important test slice annotations for fast, isolated tests.

Testing the Web Layer With @WebMvcTest

Using this annotation, you’ll get a Spring Context that includes components required for testing Spring MVC parts of your application.

What is part of the Spring Test Context? @Controller, @ControllerAdvice, @JsonComponent, Converter, Filter, WebMvcConfigurer.

What isn't part of the Spring Test Context? @Service, @Component, @Repository beans

There is also great support if you secure your endpoints with Spring Security. The annotation will auto-configure your security rules, and if you include the Spring Security Test dependency, you can easily mock the authenticated user.

As this annotation provides a mocked servlet environment, there is no port to access your application with, e.g., a RestTemplate. Therefore you use the auto-configured MockMvc to access your endpoints:

@WebMvcTest(ShoppingCartController.class)
class ShoppingCartControllerTest {

  @Autowired
  private MockMvc mockMvc;

  @MockBean
  private ShoppingCartRepository shoppingCartRepository;

  @Test
  public void shouldReturnAllShoppingCarts() throws Exception {
    when(shoppingCartRepository.findAll()).thenReturn(
      List.of(new ShoppingCart("42",
        List.of(new ShoppingCartItem(
          new Item("MacBook", 999.9), 2)
        ))));

    this.mockMvc.perform(get("/api/carts"))
      .andExpect(status().isOk())
      .andExpect(jsonPath("$[0].id", Matchers.is("42")))
      .andExpect(jsonPath("$[0].cartItems.length()", Matchers.is(1)))
      .andExpect(jsonPath("$[0].cartItems[0].item.name", Matchers.is("MacBook")))
      .andExpect(jsonPath("$[0].cartItems[0].quantity", Matchers.is(2)));
  }
}

Usually, you mock any dependent bean of your controller endpoint using @MockBean.

If you write reactive applications with WebFlux, there is also @WebFluxTest.

Testing your JPA Components With @DataJpaTest

With this annotation, you can test any JPA-related parts of your application. A good example is to verify that a native query is working as expected.

What is part of the Spring Test Context? @Repository, EntityManager, TestEntityManager, DataSource

What isn't part of the Spring Test Context? @Service, @Component, @Controller beans

By default, this annotation tries to auto-configure use an embedded database (e.g., H2) as the DataSource:

@DataJpaTest
class BookRepositoryTest {

  @Autowired
  private DataSource dataSource;

  @Autowired
  private EntityManager entityManager;

  @Autowired
  private BookRepository bookRepository;

  @Test
  public void testCustomNativeQuery() {
    assertEquals(1, bookRepository.findAll().size());

    assertNotNull(dataSource);
    assertNotNull(entityManager);
  }
}

While an in-memory database might not be a good choice to verify a native query using proprietary features, you can disable this auto-configuration with:

@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)

and use (for example) Testcontainers to create a PostgreSQL database for testing.

In addition to the auto-configuration, all tests run inside a transaction and get rolled back after their execution.

Testing JDBC Access With @JdbcTest

If your application uses the JdbcTemplate instead of JPA for the database access, Spring Boot also covers testing this slice of your application.

What is part of the Spring Test Context? JdbcTemplate, DataSource

What isn't part of the Spring Test Context? @Service, @Component, @Controller, @Repository beans

Similar to @DataJpaTest, this annotation auto-configures an embedded database for you.

@JdbcTest
public class JdbcAccessTest {

  @Autowired
  private DataSource dataSource;

  @Autowired
  private JdbcTemplate jdbcTemplate;

  @Test
  public void shouldReturnBooks() {
    assertNotNull(dataSource);
    assertNotNull(jdbcTemplate);
  }
}

Testing MongoDB Access With @DataMongoTest

Next, if your application does not use a relational database but rather a NoSQL MongoDB database, you get testing support for this.

What is part of the Spring Test Context? MongoTemplate, CrudRepository for MongoDB documents

What isn't part of the Spring Test Context? @Service, @Component, @Controller

This annotation auto-configures an embedded MongoDB database for you, like the test slice annotations for JPA and JDBC. Therefore you can use the following dependency:

<dependency><groupid>de.flapdoodle.embed</groupid>
  <artifactid>de.flapdoodle.embed.mongo</artifactid>
  <scope>test</scope>
</dependency>

And then start testing your MongoDB components:

@DataMongoTest
class ShoppingCartRepositoryTest {

  @Autowired
  private MongoTemplate mongoTemplate;

  @Autowired
  private ShoppingCartRepository shoppingCartRepository;

  @Test
  public void shouldCreateContext() {
    shoppingCartRepository.save(new ShoppingCart("42",
      List.of(new ShoppingCartItem(
        new Item("MacBook", 999.9), 2)
      )));

    assertNotNull(mongoTemplate);
    assertNotNull(shoppingCartRepository);
  }
}

Testing JSON Serialization with @JsonTest

What comes next is a less well-known test slice annotation that helps to test JSON serialization: @JsonTest

What is part of the Spring Test Context? @JsonComponent,ObjectMapper, Module from Jackson or similar components when using JSONB or GSON

What isn't part of the Spring Test Context? @Service, @Component, @Controller, @Repository

If you have more complex serialization logic for your Java classes or make use of several Jackson annotations:

public class PaymentResponse {

  @JsonIgnore
  private String id;

  private UUID paymentConfirmationCode;

  @JsonProperty("payment_amount")
  private BigDecimal amount;

  @JsonFormat(
    shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd|HH:mm:ss", locale = "en_US")
  private LocalDateTime paymentTime;

}

You can use this test slice to verify the JSON serialization of your Spring Boot application:

@JsonTest
class PaymentResponseTest {

  @Autowired
  private JacksonTester <paymentresponse>jacksonTester;

  @Autowired
  private ObjectMapper objectMapper;

  @Test
  public void shouldSerializeObject() throws IOException {

    assertNotNull(objectMapper);

    PaymentResponse paymentResponse = new PaymentResponse();
    paymentResponse.setId("42");
    paymentResponse.setAmount(new BigDecimal("42.50"));
    paymentResponse.setPaymentConfirmationCode(UUID.randomUUID());
    paymentResponse.setPaymentTime(LocalDateTime.parse("2020-07-20T19:00:00.123"));

    JsonContent<PaymentResponse>result = jacksonTester.write(paymentResponse);

    assertThat(result).hasJsonPathStringValue("$.paymentConfirmationCode");
    assertThat(result).extractingJsonPathNumberValue("$.payment_amount").isEqualTo(42.50);
    assertThat(result).extractingJsonPathStringValue("$.paymentTime").isEqualTo("2020-07-20|19:00:00");
    assertThat(result).doesNotHaveJsonPath("$.id");
  }
}

Find further information on the @JsonTest annotation in this blog post.

Testing HTTP Clients With @RestClientTest

Next comes a hidden gem when you want to test your HTTP clients with a local HTTP server.

What is part of the Spring Test Context? your HTTP client using RestTemplateBuilder, MockRestServiceServer, Jackson auto-configuration

What isn't part of the Spring Test Context? @Service, @Component, @Controller, @Repository

Using the MockRestServiceServer you can now mock different HTTP responses from the remote system:

@RestClientTest(RandomQuoteClient.class)
class RandomQuoteClientTest {

  @Autowired
  private RandomQuoteClient randomQuoteClient;

  @Autowired
  private MockRestServiceServer mockRestServiceServer;

  @Test
  public void shouldReturnQuoteFromRemoteSystem() {
    String response = "{" +
      "\"contents\": {"+
        "\"quotes\": ["+
          "{"+
            "\"author\": \"duke\"," +
            "\"quote\": \"Lorem ipsum\""+
          "}"+
        "]"+
      "}" +
    "}";

    this.mockRestServiceServer
      .expect(MockRestRequestMatchers.requestTo("/qod"))
      .andRespond(MockRestResponseCreators.withSuccess(response, MediaType.APPLICATION_JSON));

    String result = randomQuoteClient.getRandomQuote();

    assertEquals("Lorem ipsum", result);
  }
}

If your application makes use of the WebClient, you can achieve something similar.

Testing the Entire Application With @SpringBootTest

Finally, the last annotation allows writing tests against the whole application context.

What is part of the Spring Test Context? everything, TestRestTemplate (if you start the embedded servlet container)

What isn't part of the Spring Test Context? -

You can combine this annotation with other @AutoConfigure annotations (e.g. @AutoConfigureTestDatabase) to fine-tune the application context.

As now all beans are part of the Spring Context, you have to access all external resources that your application requires for startup/test execution. Again, Testcontainers can help a lot here.

@SpringBootTest
class ApplicationTests {

  @Autowired
  private RandomQuoteClient randomQuoteClient;

  @Autowired
  private ShoppingCartRepository shoppingCartRepository;

  @Autowired
  private BookRepository bookRepository;

  @Test
  void contextLoads() {
    assertNotNull(randomQuoteClient);
    assertNotNull(shoppingCartRepository);
    assertNotNull(bookRepository);
  }

}

By default, you’ll still get a mocked servlet environment and won’t occupy any local port.

If you want to start the embedded servlet container (Tomcat in most cases), you can override the webEnvironment attribute:

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class ApplicationTests {

  @Autowired
  private TestRestTemplate testRestTemplate;

  @Test
  void contextLoads() {
    assertNotNull(testRestTemplate);
  }

}

This will also auto-configure a TestRestTemplate for you to access your application on the random port. You can find more in-depth information about the @SpringBootTest annotation for writing integration tests in another guide of mine.

You can find the source code for these examples on GitHub.

Have fun using Spring Boot Test slice annotations, Phil

This is the third in a series of articles by Philip Riecks, an independent software consultant from Germany who specializes in educating Java developers about Spring Boot, AWS and testing. A version of this article was originally posted by Philip on rieckpil.de