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