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.
Write Your Own Spring Boot Test Slice
If there's no out-of-the-box test slice available for your specific tech stack, you can write your own Spring Boot test slice.
A good example may be a test slice for Kafka listeners or any other message queue.
For a blueprint on how to write your custom Spring Boot test slice, take a look at how the Spring Cloud AWS project implemented @SqsTest to test SQS listeners in isolation.
You can find the source code for these examples on GitHub.
Have fun using Spring Boot Test slice annotations,
Phil