Skip to content
Spring Boot sb testing 3 min read

@WebMvcTest & MockMvc

@WebMvcTest is a slice that boots only the Spring MVC layer — controllers, filters, @ControllerAdvice, JSON converters — and nothing else. No services, no repositories, no database. You drive it with MockMvc, which executes requests through the full MVC machinery without starting a real HTTP server. The result is a fast, focused test of your web layer.

What @WebMvcTest loads

Pointing the annotation at a single controller keeps the slice tiny and the test fast.

@WebMvcTest(ProductController.class)
class ProductControllerTest {

    @Autowired
    MockMvc mockMvc;            // auto-configured by the slice

    @MockitoBean
    ProductService service;     // the service layer is NOT loaded — we mock it
}

Because the service layer is excluded from the slice, you must provide it as a mock. @MockitoBean registers a Mockito mock in the test context, replacing the missing bean. (It is the Spring Boot 3.4+ replacement for the deprecated @MockBean.)

Performing requests and asserting

mockMvc.perform(...) sends a simulated request; andExpect(...) asserts on the result. jsonPath reads into the JSON body.

@Test
void returnsProductById() throws Exception {
    when(service.findById(1L))
            .thenReturn(Optional.of(new Product(1L, "Keyboard", new BigDecimal("49.90"))));

    mockMvc.perform(get("/api/products/1"))
            .andExpect(status().isOk())
            .andExpect(content().contentType(MediaType.APPLICATION_JSON))
            .andExpect(jsonPath("$.id").value(1))
            .andExpect(jsonPath("$.name").value("Keyboard"));
}

Static imports come from MockMvcRequestBuilders (get, post, …) and MockMvcResultMatchers (status, jsonPath, content).

Testing a POST with a JSON body

Serialize the request body and assert the created status and Location header.

@Test
void createsProduct() throws Exception {
    var saved = new Product(7L, "Mouse", new BigDecimal("19.90"));
    when(service.create(any())).thenReturn(saved);

    mockMvc.perform(post("/api/products")
                    .contentType(MediaType.APPLICATION_JSON)
                    .content("""
                        { "name": "Mouse", "price": 19.90 }
                        """))
            .andExpect(status().isCreated())
            .andExpect(header().string("Location", "/api/products/7"))
            .andExpect(jsonPath("$.id").value(7));
}

Output:

MockHttpServletResponse:
           Status = 201
    Content type = application/json
            Body = {"id":7,"name":"Mouse","price":19.90}
          Headers = [Location:"/api/products/7"]
PASSED

Testing 404 and error paths

Stub the service to return empty and assert the controller maps it to 404.

@Test
void returns404WhenMissing() throws Exception {
    when(service.findById(999L)).thenReturn(Optional.empty());

    mockMvc.perform(get("/api/products/999"))
            .andExpect(status().isNotFound());
}

If your controller relies on a @RestControllerAdvice, it is loaded by the slice, so exception-to-status mapping is tested end-to-end within the web layer. See @RestControllerAdvice.

Testing validation

@WebMvcTest loads the Bean Validation infrastructure, so @Valid constraints fire. Send an invalid body and assert 400 plus the field errors.

@Test
void rejectsBlankName() throws Exception {
    mockMvc.perform(post("/api/products")
                    .contentType(MediaType.APPLICATION_JSON)
                    .content("""
                        { "name": "", "price": -5 }
                        """))
            .andExpect(status().isBadRequest())
            .andExpect(jsonPath("$.name").value("must not be blank"))
            .andExpect(jsonPath("$.price").value("must be greater than 0"));
}

Tip: @WebMvcTest does not include @Service or @Repository beans, only @Controller/@RestController. If your test fails because a service bean is missing, that is the slice working as designed — add a @MockitoBean for it.

MockMvcTester — the AssertJ-native API

Spring Framework 6.2 / Spring Boot 3.4 added MockMvcTester, a fluent wrapper that integrates with AssertJ so you can chain assertThat(...) instead of andExpect(...).

@WebMvcTest(ProductController.class)
class ProductControllerTesterTest {

    @Autowired MockMvcTester mvc;
    @MockitoBean ProductService service;

    @Test
    void returnsProduct() {
        when(service.findById(1L))
                .thenReturn(Optional.of(new Product(1L, "Keyboard", BigDecimal.TEN)));

        assertThat(mvc.get().uri("/api/products/1"))
                .hasStatusOk()
                .bodyJson().extractingPath("$.name").isEqualTo("Keyboard");
    }
}

Common matchers

MatcherAsserts
status().isOk() / .isCreated() / .isNotFound()HTTP status code
jsonPath("$.field").value(x)A JSON value
jsonPath("$.items", hasSize(3))Collection size (Hamcrest)
content().contentType(APPLICATION_JSON)Response content type
header().string("Location", path)A response header
content().string(...)Raw response body

Note: Authentication filters can interfere with @WebMvcTest. Add spring-security-test and annotate requests with with(user("alice")), or disable security for the slice when you only care about the controller logic.

Last updated June 13, 2026
Was this helpful?