@WebMvcTest and MockMvc

Testing the Web Layer

@WebMvcTest starts only the MVC part (controllers, advice, validation, Jackson). The service is replaced by a Mockito mock with @MockitoBean. MockMvc sends fake HTTP requests without a real server.

import static org.mockito.BDDMockito.given;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;

import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.test.context.bean.override.mockito.MockitoBean;

@WebMvcTest(ProductController.class)
class ProductControllerTest {

    @Autowired MockMvc mockMvc;
    @MockitoBean ProductService service;      // Spring Boot 3.4+; older versions: @MockBean

    @Test
    void getReturnsProduct() throws Exception {
        given(service.get(1L)).willReturn(new ProductResponse(1L, "ELE-1001", "Wireless Mouse",
                "2.4 GHz", new BigDecimal("799.00"), 120, "Electronics", true));

        mockMvc.perform(get("/api/products/1"))
               .andExpect(status().isOk())
               .andExpect(jsonPath("$.name").value("Wireless Mouse"))
               .andExpect(jsonPath("$.price").value(799.00));
    }

    @Test
    void getUnknownReturns404Problem() throws Exception {
        given(service.get(42L)).willThrow(new ResourceNotFoundException("Product", 42L));

        mockMvc.perform(get("/api/products/42"))
               .andExpect(status().isNotFound())
               .andExpect(content().contentType("application/problem+json"))
               .andExpect(jsonPath("$.errorCode").value("NOT_FOUND"));
    }

    @Test
    void createValidatesBody() throws Exception {
        mockMvc.perform(post("/api/products")
                       .contentType(MediaType.APPLICATION_JSON)
                       .content("""
                           {"sku":"bad","name":"","price":-1,"stock":1,"category":"Electronics"}
                           """))
               .andExpect(status().isBadRequest())
               .andExpect(jsonPath("$.errors.name").value("must not be blank"))
               .andExpect(jsonPath("$.errors.sku").value("must look like ABC-1234"));
    }
}

Reading a Failure

Example

When a test fails, MockMvc tells you exactly what differed:

./mvnw test -Dtest=ProductControllerTest

Example output

[ERROR] ProductControllerTest.getUnknownReturns404Problem:41 Status expected:<404> but was:<500>
[INFO] Tests run: 3, Failures: 1, Errors: 0, Skipped: 0
[INFO] BUILD FAILURE

Here the mock threw an exception that no specific handler maps, so the catch-all Exception handler answered 500. @WebMvcTest does load @RestControllerAdvice beans, so check that the mock throws the exact exception type your handler expects.

Tip: Add .andDo(print()) to a perform(...) chain to print the full request and response while debugging.