@MockBean doesn't work for @WebMvcTest with JUnit 5 and Spring Boot 2?

3 min read 06-10-2024
@MockBean doesn't work for @WebMvcTest with JUnit 5 and Spring Boot 2?


@MockBean Not Working with @WebMvcTest in Spring Boot 2: A Troubleshooting Guide

Problem: You're using Spring Boot 2 with JUnit 5 and @WebMvcTest for testing your controller. You're using @MockBean to mock dependencies, but the mocks aren't working as expected.

Solution: This is a common problem that arises from the way Spring Boot handles dependency injection within the @WebMvcTest context. Let's dive into the issue and see how to fix it.

Scenario:

Imagine you have a controller, UserController, that relies on a UserService to fetch user data:

@RestController
public class UserController {

    private final UserService userService;

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

    @GetMapping("/users/{id}")
    public User getUser(@PathVariable Long id) {
        return userService.getUserById(id);
    }
}

You want to test this controller using @WebMvcTest, and mock the UserService to control the returned data. This is where @MockBean comes in:

@WebMvcTest(UserController.class)
class UserControllerTest {

    @MockBean
    private UserService userService;

    @Autowired
    private MockMvc mockMvc;

    @Test
    void testGetUser() throws Exception {
        // Setup mocked behavior for userService
        when(userService.getUserById(anyLong())).thenReturn(new User(1L, "Test User"));

        mockMvc.perform(get("/users/1"))
                .andExpect(status().isOk())
                .andExpect(content().json("{\"id\": 1, \"name\": \"Test User\"}"));
    }
}

However, you might encounter the issue where the mocked UserService isn't being used in the test, and instead, the real UserService is being injected, causing the test to fail.

Explanation:

The problem arises because @WebMvcTest focuses on testing your controllers and the web layer. It doesn't include the entire application context, which means it might not include your UserService implementation unless it's directly related to the web layer.

Solution:

There are two main ways to address this:

  1. Explicitly Import UserService: You can import UserService in your @WebMvcTest annotation using the import attribute:

    @WebMvcTest(controllers = UserController.class, import = UserService.class)
    class UserControllerTest {
        // ...
    }
    

    This ensures that Spring Boot includes UserService in the @WebMvcTest context and allows @MockBean to work correctly.

  2. Use @SpringBootTest: If you need to mock dependencies outside the web layer, it's better to use @SpringBootTest instead of @WebMvcTest. @SpringBootTest starts the full Spring context, allowing you to mock any dependency, including services like UserService. However, be mindful that @SpringBootTest might be slower and require more setup.

Additional Tips:

Example:

Here's an example of how to use the import attribute with @WebMvcTest:

@WebMvcTest(controllers = UserController.class, import = UserService.class)
class UserControllerTest {
    @MockBean
    private UserService userService;

    // ... your test code
}

By understanding the nuances of Spring Boot's dependency injection and @WebMvcTest, you can effectively mock dependencies and ensure robust testing of your controllers. Remember to choose the most appropriate testing approach based on your needs and the complexity of your application.