Home/Learn/Spring Boot/@WebMvcTest

@WebMvcTest

Intermediate
Testing

Slices the context to only MVC infrastructure and the specified controller, making tests lighter; beans not in scope must be mocked with @MockBean.

Overview

@WebMvcTest is a Spring Boot test slice that loads only the MVC layer: controllers, @ControllerAdvice, filters, WebMvcConfigurer, and Jackson converters — but NOT service beans, repositories, or @Component classes. This makes the test context much lighter than @SpringBootTest while still exercising the real MVC pipeline (unlike standalone MockMvc). Any bean the controller depends on must be provided with @MockBean. Spring Security is auto-configured if spring-security is on the classpath; for security tests, use @WithMockUser or configure a custom SecurityFilterChain test bean. @WebMvcTest is the recommended approach for controller-layer unit tests in Spring Boot.

@WebMvcTest controller slice with @MockBean

Specify the controller class to test. All its dependencies must be mocked. MockMvc is auto-wired.

Java — @WebMvcTest with @MockBean service
@WebMvcTest(OrderController.class)  // only loads OrderController + MVC infrastructure
class OrderControllerTest {

    @Autowired
    private MockMvc mockMvc;

    @Autowired
    private ObjectMapper objectMapper;

    @MockBean                           // provides mock to OrderController
    private OrderService orderService;

    @MockBean
    private OrderMapper orderMapper;

    @Test
    void getOrder_found_returns200() throws Exception {
        OrderDto dto = new OrderDto(1L, "PENDING", new BigDecimal("99.99"));
        when(orderService.findById(1L)).thenReturn(dto);

        mockMvc.perform(get("/api/orders/1"))
            .andExpect(status().isOk())
            .andExpect(jsonPath("$.id").value(1))
            .andExpect(jsonPath("$.status").value("PENDING"));
    }

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

        mockMvc.perform(get("/api/orders/99"))
            .andExpect(status().isNotFound())
            .andExpect(jsonPath("$.message").value("Order 99 not found"));
    }

    @Test
    void createOrder_invalidBody_returns400() throws Exception {
        // empty customerId triggers @NotBlank validation
        String json = """{"customerId":"","total":50.0}""";

        mockMvc.perform(post("/api/orders")
                .contentType(MediaType.APPLICATION_JSON)
                .content(json))
            .andExpect(status().isBadRequest());
    }
}

@WebMvcTest with Spring Security

Spring Security is included in the slice. By default all endpoints require authentication. Use @WithMockUser or disable security for specific tests.

Java — @WebMvcTest with @WithMockUser security
@WebMvcTest(OrderController.class)
class OrderSecurityTest {

    @Autowired
    private MockMvc mockMvc;

    @MockBean
    private OrderService orderService;

    // Include if your SecurityConfig depends on beans not in the slice
    @MockBean
    private UserDetailsService userDetailsService;

    @Test
    @WithMockUser(username = "john", roles = "USER")
    void getOrder_withUser_returns200() throws Exception {
        when(orderService.findById(1L)).thenReturn(new OrderDto(1L, "PENDING", BigDecimal.TEN));
        mockMvc.perform(get("/api/orders/1"))
            .andExpect(status().isOk());
    }

    @Test
    @WithMockUser(roles = "ADMIN")
    void deleteOrder_withAdmin_returns204() throws Exception {
        mockMvc.perform(delete("/api/orders/1"))
            .andExpect(status().isNoContent());
    }

    @Test
    void getOrder_unauthenticated_returns401() throws Exception {
        mockMvc.perform(get("/api/orders/1"))
            .andExpect(status().isUnauthorized());
    }

    // Exclude security for a specific test class
    @Import(NoSecurityConfig.class)
    static class NoSecurityConfig {
        @Bean SecurityFilterChain noSecurity(HttpSecurity http) throws Exception {
            http.authorizeHttpRequests(a -> a.anyRequest().permitAll());
            return http.build();
        }
    }
}

Testing @ControllerAdvice exception handlers

@WebMvcTest automatically includes @ControllerAdvice beans. Test exception handler mapping without needing the full application context.

Java — testing @ControllerAdvice in @WebMvcTest
// GlobalExceptionHandler — auto-detected by @WebMvcTest
@RestControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(OrderNotFoundException.class)
    public ResponseEntity<ErrorResponse> handleNotFound(OrderNotFoundException ex) {
        return ResponseEntity.status(HttpStatus.NOT_FOUND)
            .body(new ErrorResponse(ex.getMessage()));
    }

    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ResponseEntity<ValidationErrorResponse> handleValidation(
            MethodArgumentNotValidException ex) {
        List<String> errors = ex.getBindingResult()
            .getFieldErrors()
            .stream()
            .map(e -> e.getField() + ": " + e.getDefaultMessage())
            .collect(Collectors.toList());
        return ResponseEntity.badRequest()
            .body(new ValidationErrorResponse(errors));
    }
}

// Test — @ControllerAdvice is included automatically in @WebMvcTest slice
@Test
void handlerThrowsNotFoundException_returns404WithMessage() throws Exception {
    when(orderService.findById(1L)).thenThrow(new OrderNotFoundException(1L));

    mockMvc.perform(get("/api/orders/1"))
        .andExpect(status().isNotFound())
        .andExpect(jsonPath("$.message").value("Order 1 not found"));
}

Key Points to Remember

  • 1@WebMvcTest loads only MVC infrastructure — faster than @SpringBootTest but tests the real MVC pipeline.
  • 2All service/repository beans must be provided via @MockBean; they are not auto-scanned in the slice.
  • 3Spring Security IS included; use @WithMockUser, @WithAnonymousUser, or mock UserDetailsService as needed.
  • 4@ControllerAdvice beans are automatically included in the slice — test exception handler mappings here.
  • 5Specify the controller class in @WebMvcTest(MyController.class) to load only that controller and reduce context size.
  • 6Use @Import to add custom beans or configuration to the slice without loading the full application context.

Interview Questions

Sign in to ask Aria
1

What beans does @WebMvcTest load and what does it exclude?

EasyAmazon
2

Why would you use @WebMvcTest instead of @SpringBootTest for controller tests?

MediumNetflix
3

How does @WebMvcTest handle Spring Security and how do you test secured endpoints?

MediumGoogle
4

What is the difference between @MockBean and @Mock, and which is required in @WebMvcTest?

MediumShopify
5

How would you test an endpoint that uploads a file using @WebMvcTest?

HardUber

Ask Aria about @WebMvcTest

Your personal AI tutor — ask anything about this concept

Revision Status

Personal Notes

Sign in to save personal notes for this topic.

Discussion

Sign in to join the discussion.

Loading discussion…