You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.02 22:46:23