SpringBootTest中Mock后Autowired服务传Null等集成测试问题
我来逐个帮你解决这些问题,顺便梳理下正确的集成测试写法:
问题1:执行代码时提示“cannot call real method on abstract methods”
这个错误的原因很直白:你用@MockBean Mock了TestService接口(它是抽象的,没有具体实现),然后调用thenCallRealMethod()——抽象方法根本没有真实实现可以调用!而且你完全没必要Mock TestController本身,集成测试的核心就是让Spring加载真实的控制器实例,通过MockMvc去调用它的接口,而非Mock控制器。
直接删掉测试类里的@MockBean TestController testController;,同时移除针对控制器的Mockito.when(...)调用,这部分完全是多余的。
问题2:当Mock TestServiceImpl时,控制器中的TestService出现NullPointerException
空指针的根源在于:你的控制器依赖的是TestService接口,但你Mock的是TestServiceImpl实现类——Spring容器里会同时存在真实的TestService接口实现和你Mock的实现类,导致控制器无法正确注入Mock实例。
正确的做法是Mock接口而非实现类:使用@MockBean TestService testService;,这样Spring会自动把这个Mock实例注入到控制器中,空指针问题就解决了。
问题3:使用MongoDB时如何Mock仓库层?尝试Mock MongoTemplate时提示“MongoConvertor must not be null”
别直接Mock MongoTemplate,这玩意儿内部依赖复杂,Mock起来非常麻烦。Spring Data的Repository本身就是接口,直接Mock你的TestRepository接口才是最简洁的方案:
用@MockBean TestRepository repo;声明Mock实例,然后针对Repository的方法做Stub即可,示例:
Mockito.when(repo.save(Mockito.any(StringEntity.class))).thenReturn(new StringEntity("dude"));
这种方式既避免了操作真实数据库,又不用管MongoTemplate的内部依赖问题。
问题4:当前测试用例写法是否正确?不使用thenCallRealMethod()能否获得代码覆盖率?
当前的写法完全不正确,核心错误包括:
- Mock了不需要Mock的控制器
- 对抽象接口调用
thenCallRealMethod() - 没有正确Stub依赖的底层方法
关于代码覆盖率:如果想要覆盖控制器和Service的真实代码,你需要让这些真实代码运行起来,而非只Mock它们返回固定值。正确的姿势是:
- 让Spring加载真实的
TestController和TestServiceImpl实例 - Mock底层的
TestRepository(避免真实DB操作) - 通过
MockMvc调用接口,让控制器调用真实的Service方法,Service再调用Mock的Repository方法
这样既完成了集成测试(覆盖控制器→Service的完整流程),又能获得控制器和Service的代码覆盖率,完全不需要用thenCallRealMethod()。
修正后的完整测试用例
@RunWith(SpringRunner.class) @SpringBootTest @AutoConfigureMockMvc public class MockitoTestingApplicationTests { @Autowired private MockMvc mvc; // Mock底层仓库,避免真实数据库操作 @MockBean private TestRepository repo; @Test public void welcomeMessage_ShouldReturnOkAndCorrectContent() throws Exception { // Stub仓库的save方法,返回任意符合要求的实例即可 Mockito.when(repo.save(Mockito.any(StringEntity.class))).thenReturn(new StringEntity("dude")); // 调用接口并验证状态和返回内容 mvc.perform(get("/api/v1/welcome").param("name", "dude")) .andExpect(status().isOk()) .andExpect(content().string("Hello dude")); } }
额外提示:如果你的测试只需要覆盖控制器层(不需要加载整个Spring上下文),可以用@WebMvcTest(TestController.class)代替@SpringBootTest,测试启动会更快——它只会加载控制器相关的Bean,再用@MockBean注入TestService即可。
内容的提问来源于stack exchange,提问作者Krishna

