पाठ 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.

Three ideas: web slice tests, data slice tests, the context-loading pitfall.
Figure 5.1 — Web slices, data slices and pitfalls.

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

त्वरित जाँच: 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.