Spring Boot @WebMvcTest Annotation | Unit Testing Controllers

🚀 Introduction: What is @WebMvcTest in Spring Boot?

The @WebMvcTest annotation in Spring Boot is used for unit testing controllers in a Spring MVC application. It loads only the web layer (controllers and related components) while excluding service and repository layers.

Key Features of @WebMvcTest:
✔ Loads only Spring MVC components (controllers, @ControllerAdvice, filters).
✔ Uses MockMvc to simulate HTTP requests.
✔ Excludes service and repository layers for lightweight tests.
✔ Supports mocking dependencies using @MockitoBean.

📌 In this guide, you’ll learn:
How to test controllers using @WebMvcTest.
How it differs from @SpringBootTest.
Best practices for writing efficient controller tests.

1️⃣ Difference Between @WebMvcTest and @SpringBootTest

Annotation Purpose Components Loaded
@WebMvcTest Unit testing controllers Controllers, @ControllerAdvice, filters, MockMvc
@SpringBootTest Full application testing Entire application context (controllers, services, repositories, etc.)

📌 When to Use @WebMvcTest?
✔ When testing only the controller layer (unit tests).
✔ When mocking services instead of loading full application context.
✔ When testing request validation, exception handling, and request mappings.

2️⃣ Basic Example: Using @WebMvcTest to Test a Controller

📌 Example: Testing a REST Controller with @WebMvcTest

1. Controller (UserController.java)

@RestController
@RequestMapping("/api/users")
public class UserController {

    @GetMapping("/{id}")
    public ResponseEntity<String> getUserById(@PathVariable int id) {
        if (id == 1) {
            return ResponseEntity.ok("User: Ramesh");
        }
        return ResponseEntity.status(HttpStatus.NOT_FOUND)
                .body("User not found");
    }
}

2. Test Class (UserControllerTest.java)

@WebMvcTest(UserController.class)
class UserControllerTest {

    @Autowired
    private MockMvc mockMvc;

    @Test
    void testGetUserById_Success() throws Exception {
        mockMvc.perform(get("/api/users/1"))
                .andExpect(status().isOk())
                .andExpect(content().string("User: Ramesh"));
    }

    @Test
    void testGetUserById_NotFound() throws Exception {
        mockMvc.perform(get("/api/users/99"))
                .andExpect(status().isNotFound())
                .andExpect(content().string("User not found"));
    }
}

📌 Test Output:

Test passed ✅

The test verifies that the controller handles different cases correctly.
Using MockMvc, we simulate HTTP requests without starting a real server.

3️⃣ Using @MockitoBean to Inject Mocks into Controllers

Since @WebMvcTest does not load service or repository beans, you must use @MockitoBean to mock service dependencies.

📌 Example: Testing Controller with a Mocked Service

1. Service Layer (UserService.java)

@Service
public class UserService {
    public String getUserById(int id) {
        return id == 1 ? "Ramesh" : null;
    }
}

2. Controller (UserController.java)

@RestController
@RequestMapping("/api/users")
public class UserController {

    private final UserService userService;

    public UserController(UserService userService) {
        this.userService = userService;
    }

    @GetMapping("/{id}")
    public ResponseEntity<String> getUserById(@PathVariable int id) {
        String user = userService.getUserById(id);
        if (user != null) {
            return ResponseEntity.ok(user);
        }
        return ResponseEntity.status(HttpStatus.NOT_FOUND).body("User not found");
    }
}

3. Test Class with @MockitoBean (UserControllerTest.java)

@WebMvcTest(UserController.class)
class UserControllerTest {

    @Autowired
    private MockMvc mockMvc;

    @MockitoBean
    private UserService userService;

    @Test
    void testGetUserById_Success() throws Exception {
        when(userService.getUserById(1)).thenReturn("Ramesh");

        mockMvc.perform(get("/api/users/1"))
                .andExpect(status().isOk())
                .andExpect(content().string("Ramesh"));
    }

    @Test
    void testGetUserById_NotFound() throws Exception {
        when(userService.getUserById(99)).thenReturn(null);

        mockMvc.perform(get("/api/users/99"))
                .andExpect(status().isNotFound())
                .andExpect(content().string("User not found"));
    }
}

📌 Test Output:

Test passed ✅

@MockitoBean replaces the real UserService with a mock instance.
Ensures that controller tests focus only on HTTP handling, not actual service logic.

4️⃣ Testing @PostMapping with JSON Request Body

📌 Example: Testing JSON Input with MockMvc

1. Controller (UserController.java)

@RestController
@RequestMapping("/api/users")
public class UserController {

    @PostMapping
    public ResponseEntity<String> createUser(@RequestBody User user) {
        return ResponseEntity.status(HttpStatus.CREATED)
                .body("User " + user.getName() + " created successfully!");
    }
}

2. Test Class (UserControllerTest.java)

@WebMvcTest(UserController.class)
class UserControllerTest {

    @Autowired
    private MockMvc mockMvc;

    @Test
    void testCreateUser() throws Exception {
        String userJson = """
            {
              "name": "Ramesh",
              "email": "ramesh@example.com"
            }
        """;

        mockMvc.perform(post("/api/users")
                .contentType(MediaType.APPLICATION_JSON)
                .content(userJson))
                .andExpect(status().isCreated())
                .andExpect(content().string("User Ramesh created successfully!"));
    }
}

📌 Test Output:

Test passed ✅

Tests that JSON requests are properly handled by the controller.
Ensures correct HTTP status codes (201 Created).

5️⃣ Testing Exception Handling with @ControllerAdvice

📌 Example: Testing Custom Exception Handling

1. Exception Class (UserNotFoundException.java)

@ResponseStatus(HttpStatus.NOT_FOUND)
public class UserNotFoundException extends RuntimeException {
    public UserNotFoundException(String message) {
        super(message);
    }
}

2. Global Exception Handler (GlobalExceptionHandler.java)

@RestControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(UserNotFoundException.class)
    public ResponseEntity<String> handleUserNotFound(UserNotFoundException ex) {
        return ResponseEntity.status(HttpStatus.NOT_FOUND).body(ex.getMessage());
    }
}

3. Test Case (UserControllerTest.java)

@WebMvcTest(UserController.class)
class UserControllerTest {

    @Autowired
    private MockMvc mockMvc;

    @Test
    void testUserNotFoundException() throws Exception {
        mockMvc.perform(get("/api/users/99"))
                .andExpect(status().isNotFound())
                .andExpect(content().string("User not found"));
    }
}

Verifies that exceptions are correctly handled at the global level.

🎯 Summary: Best Practices for Using @WebMvcTest

Use @WebMvcTest for unit testing controllers.
Use MockMvc to simulate HTTP requests.
Use @MockitoBean to mock service dependencies.
Test JSON requests and responses properly.
Ensure exception handling works as expected.

🚀 Following these best practices ensures efficient and maintainable controller tests in Spring Boot!

Comments

Spring Boot 3 Paid Course Published for Free
on my Java Guides YouTube Channel

Subscribe to my YouTube Channel (165K+ subscribers):
Java Guides Channel

Top 10 My Udemy Courses with Huge Discount:
Udemy Courses - Ramesh Fadatare