Spring Boot MockMvc测试遇UnnecessaryStubbingException及Mock值失效问题
问题解决方向
核心矛盾分析
你遇到的问题本质是Mockito的@InjectMocks与Spring推荐的构造注入不兼容,同时Spring Boot 3.x默认禁用了隐式字段注入,导致修改控制器后测试正常但生产环境空指针。
1. 修复单元测试(保留构造注入,解决Mock不生效+UnnecessaryStubbingException)
替换@Mock+@InjectMocks为@MockBean
不要修改控制器的构造注入(保持final成员变量+构造注入,这是Spring官方推荐的方式),改用Spring Boot提供的@MockBean注解:
@MockBean会自动将Mock的Service实例替换到Spring测试上下文,MockMvc调用控制器时能正确拿到Mock的Service- 无需手动处理构造注入的参数匹配,比Mockito的
@InjectMocks更适配Spring环境
处理UnnecessaryStubbingException
这个异常是因为你Stub了某个Service方法但测试中并未调用,解决方式二选一:
- 直接删除测试中未用到的
when(xxx).thenReturn(xxx)代码 - 在测试类上添加
@MockitoSettings(strictness = Strictness.LENIENT),放宽Mockito的严格检查规则
正确的测试类示例
@SpringBootTest @AutoConfigureMockMvc class BookControllerTest { @Autowired private MockMvc mockMvc; @MockBean // 用这个替换@Mock+@InjectMocks private BookService bookService; @Test void getAllBooks_ShouldReturn3Books() throws Exception { // 仅Stub测试需要用到的方法 List<Book> mockBooks = List.of( new Book(1L, "Book1"), new Book(2L, "Book2"), new Book(3L, "Book3") ); when(bookService.getAllBooks()).thenReturn(mockBooks); // 执行请求并验证 mockMvc.perform(get("/api/v1/book") .contentType(MediaType.APPLICATION_JSON)) .andExpect(status().isOk()) .andExpect(jsonPath("$", hasSize(3))) .andExpect(jsonPath("$[0].title").value("Book1")); } }
2. 修复生产环境空指针异常
确保控制器和Service的Spring配置完全符合规范:
- 控制器保持构造注入:不要改成非
final字段+无构造方法,Spring Boot 3.x默认不支持隐式字段注入(即不加@Autowired的字段注入),必须用构造注入 - Service类正确标记:BookService的实现类必须添加
@Service注解,让Spring能扫描并创建实例 - 组件扫描范围正确:控制器和Service类要放在Spring Boot主启动类的包或子包下,否则需要在主启动类添加
@ComponentScan(basePackages = "xxx")指定扫描路径 - 多实现类处理:如果BookService有多个实现类,要在控制器构造方法上用
@Qualifier("xxx")指定注入的实例,或者给主实现类加@Primary注解
正确的控制器示例
@RestController @RequestMapping("/api/v1/book") public class BookController { // 保持final字段+构造注入 private final BookService bookService; public BookController(BookService bookService) { this.bookService = bookService; } @GetMapping public List<Book> getAllBooks() { return bookService.getAllBooks(); } }
3. 为什么之前的修改会导致生产环境异常?
当你把控制器改成非final字段并移除构造注入后:
- 测试时Mockito的
@InjectMocks可以通过反射直接给字段赋值,所以Mock生效 - 但生产环境中,Spring默认优先用构造注入,此时控制器没有构造方法,Spring会创建无参实例,又因为Spring Boot 3.x默认禁用隐式字段注入,未加
@Autowired的bookService字段不会被注入,最终导致空指针。
内容的提问来源于stack exchange,提问作者Idur Jul
相关产品推荐
相关产品推荐

