为何Mockito中@MockBean可用,@SpyBean却抛出InvalidUseOfMatchersException?
问题现象
用@SpyBean标注Spring Data仓库接口(该接口继承自带有实现类BaseRepositoryImpl的BaseRepository,最终继承自Spring Data的Repository)时,使用Mockito参数匹配器(比如eq(true))存根方法会抛出异常:
org.mockito.exceptions.misusing.InvalidUseOfMatchersException:
Invalid use of argument matchers!
0 matchers expected, 1 recorded
换成@MockBean标注仓库、或者直接传入真实值(比如用true替代eq(true))时,@SpyBean就能正常工作。另外业务场景中需要用any()这类匹配器处理保存操作——因为待保存对象的具体内容无法提前预知。
原因
@SpyBean会生成真实Bean的Spy实例,而Spring Data仓库的动态代理逻辑和Mockito的参数匹配器机制存在冲突。Spring Data通过动态代理生成仓库实现的过程中,方法调用的代理逻辑会干扰Mockito对参数匹配器的计数校验,导致匹配器使用无效。
解决方案
1. 调整Spy对象的存根方式
如果必须使用@SpyBean,针对需要用匹配器的场景,改用doAnswer替代直接的when/doThrow,通过自定义逻辑处理参数匹配:
// 处理保存操作(无法预知对象内容的场景) doAnswer(invocation -> { YourEntity savedEntity = invocation.getArgument(0); // 可添加自定义校验逻辑,比如检查实体属性合法性 return savedEntity; }).when(mockitoRepository).save(any(YourEntity.class)); // 处理抛异常场景(若doThrow仍报错,改用doAnswer手动抛异常) doAnswer(invocation -> { throw new RuntimeException(); }).when(mockitoRepository).customFindAll(eq(true));
2. 手动创建Spy实例替代@SpyBean
绕过Spring的@SpyBean自动代理,手动基于真实实现类创建Spy,再注入到测试对象中:
@Autowired private BaseRepositoryImpl baseRepositoryImpl; private YourRepository mockitoRepository; @BeforeEach void setUp() { // 基于真实实现类创建Spy mockitoRepository = Mockito.spy(baseRepositoryImpl); // 将Spy注入到被测试服务(比如通过setter赋值) yourService.setRepository(mockitoRepository); }
3. 存根用真实值,验证时用匹配器
对于Spy对象,存根方法时传入真实值避免触发匹配器异常,在方法调用后通过verify结合匹配器验证参数:
// 存根时用真实值 Mockito.when(mockitoRepository.customFindAll(true)).thenThrow(RuntimeException.class); // 执行测试逻辑后,验证保存方法的参数(处理无法预知对象的场景) verify(mockitoRepository).save(argThat(entity -> entity.getStatus().equals(EntityStatus.NEW) ));
内容的提问来源于stack exchange,提问作者KisNiko

