Spring Boot offers great support to test different slices (web, database, etc.) of your 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 to write isolated and fast 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's part of the Spring Test Context: @Controller
, @ControllerAdvice
, @JsonComponent
, Converter
, Filter
, WebMvcConfigurer
What's not part of the Spring Test Context: @Service
, @Component
, @Repository
beans
Furthermore, 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 rather use the auto-configured MockMvc
to access your endpoints:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | @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's part of the Spring Test Context: @Repository
, EntityManager
, TestEntityManager
, DataSource
What's not 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
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | @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:
1 | @AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) |
and use, e.g., 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's part of the Spring Test Context: JdbcTemplate
, DataSource
What's not part of the Spring Test Context: @Service
, @Component
, @Controller
, @Repository
beans
Similar to @DataJpaTest
, this annotation auto-configures an embedded database for you.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | @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's part of the Spring Test Context: MongoTemplate
, CrudRepository
for MongoDB documents
What's not 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:
1 2 3 4 5 | <dependency> <groupId>de.flapdoodle.embed</groupId> <artifactId>de.flapdoodle.embed.mongo</artifactId> <scope>test</scope> </dependency> |
And then start testing your MongoDB components:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | @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 more unknown test slice annotation that helps to test JSON serialization: @JsonTest
What's part of the Spring Test Context: @JsonComponent
,ObjectMapper
, Module
from Jackson or similar components when using JSONB or GSON
What's not part of the Spring Test Context: @Service
, @Component
, @Controller
, @Repository
If you have a more complex serialization logic for your Java classes or make use of several Jackson annotations:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | 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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | @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 a previous 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's part of the Spring Test Context: your HTTP client using RestTemplateBuilder
, MockRestServiceServer
, Jackson auto-configuration
What's not part of the Spring Test Context: @Service
, @Component
, @Controller
, @Repository
Using the MockRestServiceServer
you can now mock different HTTP responses from the remote system:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | @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); } } |
You can find a demonstration of this annotation on YouTube.
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's part of the Spring Test Context: everything, TestRestTemplate
(if you start the embedded servlet container)
What's not 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.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | @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:
1 2 3 4 5 6 7 8 9 10 11 12 | @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.
If you want a more practical deep-dive for these Spring Boot Test Slices, consider enrolling in the Testing Spring Boot Applications Masterclass. With this comprehensive online course, you'll learn how to apply these annotations for a real-world application (Java 16, a recent Spring Boot version, ReactJS, TypeScript, AWS, etc.).
You can find the source code for these examples on GitHub.
Have fun using Spring Boot Test slice annotations,
Phil
[…] our test. Most of the time we either populate the full Spring Context (@SpringBootTest) or use a sliced context (e.g. @WebMvcTest or @DataJpaTest). This allows us to test the integration of multiple classes or […]
[…] any underlying HTTP communication. This is great when it comes to performance, as these tests only use a sliced context and don't have to start the embedded Servlet container. On the other side, such tests don't exactly […]
[…] context for this would be overkill. Spring Boot provides a test annotation that allows us to start a sliced Spring Context with only relevant JPA components: […]
[…] Spring Boot Test Slice annotations include the property attribute. This allows adding properties to the Spring Environment before […]
[…] test with @WebMvcTest. This annotation not only ensures to auto-configure MockMvc but also create a sliced Spring context containing only MVC related […]
[…] >> Spring Boot Test Slices Overview and Usage [rieckpil.de] […]
[…] is one of the NoSQL databases that Spring Boot offers great testing support for. Like all other test slice annotations from Spring Boot, when using @DataMongoTest, we'll get a Spring Test Context with just enough beans to test any […]
[…] relevant to testing a particular part of your application. I've dedicated an entire article to introduce the most common of these annotations and explain their […]
[…] take care of auto-configuring Spring's ApplicationContext to include all beans you need. The most known ones are those of testing your web layer or database layer in isolation. There is also a lesser-known […]