构造器传参类使用@SpyBean时Mockito存根报错如何解决
问题根因
@SpyBean注解的作用是让Spring容器在初始化上下文时,生成被Mockito增强的Spy代理对象放入容器,你在@BeforeEach方法中手动new了一个普通的ComicService实例赋值给该字段,直接覆盖了原有的Spy代理。此时字段指向的是没有Mockito增强逻辑的普通Java对象,既无法支持打桩操作,参数匹配器执行后也无法关联到打桩流程,直接触发你看到的参数匹配器使用报错。- Spy对象打桩存在语法限制:使用
Mockito.when(调用方法).thenReturn()语法打桩Spy方法时,会先真实执行一次原方法,getHash方法里有加密逻辑、依赖类成员变量,直接执行很容易触发意外异常,打桩Spy方法必须使用doReturn系列语法。
修复步骤
删除
setUp方法中手动实例化ComicService的代码,通过测试配置给构造器注入所需的密钥参数,让Spring自动完成@SpyBean实例的创建。
最简单的方式是在测试类上添加@TestPropertySource注解注入配置:@ExtendWith(SpringExtension.class) @ActiveProfiles("test") // 直接注入构造器需要的配置值 @TestPropertySource(properties = { "marvel.public_key=ae78641e8976ffdf3fd4b71254a3b9bf", "marvel.private_key=eb9fd0d8r8745cd0d554fb2c0e7896dab3bb745" }) public class ComicServiceTest { // 其余字段保持不变 @SpyBean ComicService comicService; @MockBean MarvelClient marvelClient; // 删掉setUp里手动new对象的逻辑,不需要额外初始化 }你也可以把这两个配置写到
application-test.yml/application-test.properties测试配置文件里,效果一致。替换
getHash方法的打桩语法,改用Spy对象专用的doReturn().when()写法,避免真实执行原方法:
把原来报错的打桩代码:Mockito.when(comicService.getHash(Mockito.anyString())).thenReturn("c6fc42667498ea8081a22f4570b42d03");替换为:
Mockito.doReturn("c6fc42667498ea8081a22f4570b42d03") .when(comicService) .getHash(Mockito.anyString());
修复后完整测试类示例
@ExtendWith(SpringExtension.class) @ActiveProfiles("test") @TestPropertySource(properties = { "marvel.public_key=ae78641e8976ffdf3fd4b71254a3b9bf", "marvel.private_key=eb9fd0d8r8745cd0d554fb2c0e7896dab3bb745" }) public class ComicServiceTest { @SpyBean ComicService comicService; @MockBean MarvelClient marvelClient; @Test public void getComicByApiTest() { // 构造测试返回数据 MarvelAPIModelDTO foundMarvelAPIModelDTO = createMarvelAPIModelDTO(); // 打桩同类方法getHash Mockito.doReturn("c6fc42667498ea8081a22f4570b42d03") .when(comicService) .getHash(Mockito.anyString()); // 打桩外部依赖mock的方法 Mockito.when(marvelClient.getComic(Mockito.anyInt(), Mockito.anyString(), Mockito.anyString(), Mockito.anyString())) .thenReturn(foundMarvelAPIModelDTO); // 执行被测方法 MarvelAPIModelDTO marvelAPIModelDTO = comicService.getComicByApi(1); // 结果断言 Assertions.assertThat(marvelAPIModelDTO.getData().getResults().get(0).getId()).isEqualTo(1); } }
补充:如果是纯单元测试不需要加载Spring上下文,可以不用
@SpyBean/@MockBean,直接用@Spy、@Mock、@InjectMocks组合,手动构造实例后用Mockito.spy()包装即可,Spring Boot集成测试场景下优先用上述@SpyBean+配置注入的方案,保证对象被Spring容器正确管理。
内容的提问来源于stack exchange,提问作者Gustavo Cruz
相关产品推荐
相关产品推荐

