How do you test a REST controller with MockMvc?

8 minintermediatemockmvctestingspring-mvc

Quick Answer

MockMvc simulates HTTP requests against Spring MVC's dispatch mechanism without starting a real HTTP server or opening a real network socket, letting you assert on the resulting status code, headers, and response body using a fluent API. It's typically auto-configured by @WebMvcTest (with collaborating service beans replaced by mocks) or obtained via MockMvcBuilders/@AutoConfigureMockMvc when using a fuller @SpringBootTest context, and is significantly faster than spinning up a real embedded server and issuing actual HTTP calls with a client like RestTemplate/WebTestClient.

Detailed Answer

MockMvc lets you test Spring MVC controllers by simulating the request-dispatch mechanism itself — routing, argument resolution, message conversion, exception handling — without starting a real embedded server or making an actual network call, which makes it both fast and focused purely on the web layer's behavior.

Typical setup with @WebMvcTest:

@WebMvcTest(OrderController.class)
class OrderControllerTest {
    @Autowired MockMvc mockMvc;
    @MockitoBean OrderService orderService; // real service replaced with a mock for this test

    @Test
    void createOrder_returnsCreatedOrderWithLocationHeader() throws Exception {
        Order created = new Order(1L, "PENDING");
        when(orderService.create(any())).thenReturn(created);

        mockMvc.perform(post("/orders")
                .contentType(MediaType.APPLICATION_JSON)
                .content("""
                    {"customerId": "cust-1", "quantity": 2}
                    """))
            .andExpect(status().isCreated())
            .andExpect(header().string("Location", "/orders/1"))
            .andExpect(jsonPath("$.id").value(1))
            .andExpect(jsonPath("$.status").value("PENDING"));
    }

    @Test
    void getOrder_returns404WhenMissing() throws Exception {
        when(orderService.getOrder(99L)).thenThrow(new OrderNotFoundException(99L));

        mockMvc.perform(get("/orders/99"))
            .andExpect(status().isNotFound());
    }
}

Key building blocks:

  • perform(...) builds and dispatches a simulated request (get, post, put, delete static helpers from MockMvcRequestBuilders), with fluent methods for headers, body content, and request parameters.
  • andExpect(...) chains assertions on the result — HTTP status (status().isOk()), headers (header().string(...)), and response body content, including jsonPath(...) for asserting on specific fields within a JSON response without deserializing it into a full object first.
  • @MockitoBean (the modern replacement for the deprecated @MockBean) swaps out a real collaborator bean (here, OrderService) for a Mockito mock within the test's slice of the ApplicationContext, letting the test isolate the controller's own behavior (routing, validation, serialization, error handling) from the actual business logic underneath.

Why this is preferable to spinning up a real server and making real HTTP calls for most controller tests: it's dramatically faster (no real server startup, no real sockets/ports), and it still exercises the genuine Spring MVC dispatch pipeline (validation, exception handling, JSON serialization) — closer to a true integration test of the web layer than calling the controller method directly in plain Java would be, while remaining much cheaper than a full @SpringBootTest with a real running server.