Lesson 14 / 25
Web Layer Tests With @WebMvcTest
Controllers without the rest of the app.
MockMvc and @MockitoBean
@WebMvcTest(ProductController.class) starts only the web layer (controllers, advice, JSON, validation) and provides MockMvc to perform requests without a real server. Dependencies such as services are replaced with mocks using @MockitoBean (the replacement for the older @MockBean). Assert status codes and JSON with jsonPath. These tests are fast and focus on HTTP behaviour: mapping, validation, error handling and serialisation. In Spring Boot 4 the annotation lives in the webmvc test module.
Fast slices, real assertions
Slice tests start only the layer under test; a common pitfall shows why bean placement matters.
Controller tests with MockMvc
This file is from a demo "shop" service generated by start.spring.io for Spring Boot 4.1.1 and Java 21 (Temurin 21.0.12), built with Maven 3.9.16; the project compiled and its tests passed. Three tests cover a successful lookup, a 404 problem detail and a rejected invalid body.
package com.example.shop;
import static org.mockito.BDDMockito.given;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import java.math.BigDecimal;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.webmvc.test.autoconfigure.WebMvcTest;
import org.springframework.http.MediaType;
import org.springframework.test.context.bean.override.mockito.MockitoBean;
import org.springframework.test.web.servlet.MockMvc;
@WebMvcTest(ProductController.class) // only the web layer is started
@EnableConfigurationProperties(ShopProperties.class)
class ProductControllerTest {
@Autowired MockMvc mvc;
@MockitoBean ProductService service; // the service is replaced by a mock
@Test
void returnsProduct() throws Exception {
given(service.get(1L)).willReturn(new Product("Notebook", new BigDecimal("120.00")));
mvc.perform(get("/api/products/1"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.name").value("Notebook"))
.andExpect(jsonPath("$.currency").value("INR"));
}
@Test
void unknownProductIs404ProblemDetail() throws Exception {
given(service.get(99L)).willThrow(new ProductNotFoundException(99));
mvc.perform(get("/api/products/99"))
.andExpect(status().isNotFound())
.andExpect(jsonPath("$.title").value("Product not found"));
}
@Test
void rejectsInvalidBody() throws Exception {
mvc.perform(post("/api/products").contentType(MediaType.APPLICATION_JSON)
.content("{\"name\": \"\", \"price\": 0}"))
.andExpect(status().isBadRequest());
}
}Running the tests, run
I ran mvn test (Maven 3.9.16, Java 21) on the demo; the summary lines are copied from that run.
mvn test
Output:
[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 3.749 s -- in com.example.shop.ProductRepositoryTest [INFO] Tests run: 3, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 1.423 s -- in com.example.shop.ProductControllerTest [INFO] Tests run: 4, Failures: 0, Errors: 0, Skipped: 0 [INFO] BUILD SUCCESS
Quick check: What does @WebMvcTest start?
- Only the JPA layer
- The whole application including the database
- Nothing; it is a unit test without Spring
- Only the web layer, with MockMvc and mocked dependencies
Answer
Only the web layer, with MockMvc and mocked dependencies — A focused slice.