Spring Boot 2:无参@Cacheable方法Mockito测试失效问题
问题原因分析:无参数@Cacheable方法测试失败的核心原因
这个问题的本质是Mockito存根逻辑与Spring Cache代理的交互差异,具体来说是无参数方法的存根方式触发了缓存代理的提前执行,导致缓存被无效值填充,进而破坏了测试流程。
为什么无参数方法的测试会失败?
先看你无参数方法的存根代码:
Mockito.when(keyService.getKey()).thenReturn("key1", "key2");
Mockito的这种存根方式(when(obj.method()))有个关键特性:它会先实际执行一次目标方法,再完成存根配置。结合Spring Cache代理的逻辑,整个流程变成了这样:
- 执行
when(keyService.getKey())时,会调用Spring缓存代理的getKey()方法。 - 缓存代理检查缓存,此时缓存为空,于是调用底层Mockito mock对象的
getKey()——但这时候存根还没设置,mock会返回默认值(String类型就是null)。 - 缓存代理把这个
null存入了keyCache缓存,对应的键是Spring Cache默认的无参数键SimpleKey.EMPTY。 - 直到这一步,Mockito才完成存根设置:后续调用mock的
getKey()会依次返回key1、key2。
接下来测试代码执行时:
- 第一次调用
keyService.getKey():缓存代理直接返回缓存里的null,和你断言的"key1"不符,测试直接失败。 - 就算忽略第一次的
null,第二次调用也会返回缓存里的null,同样不满足assertEquals("key1", secondCall)的断言。
为什么带参数的方法测试能成功?
再看带参数方法的存根代码:
Mockito.when(keyService.getKey(Mockito.anyString())).thenReturn("key1", "key2");
这里用到了Mockito的参数匹配器(anyString()),它会触发Mockito的延迟存根模式:不会实际调用代理的getKey()方法,而是直接为方法设置存根规则,完全跳过了Spring缓存代理的拦截逻辑,也就不会提前向缓存里存入无效值。
测试执行流程就完全符合预期了:
- 第一次调用
keyService.getKey("xyz"):缓存代理检查缓存为空,调用mock方法返回key1,并把key1存入缓存(键对应参数"xyz")。 - 第二次调用
keyService.getKey("xyz"):缓存代理直接从缓存取出key1返回,不会再调用mock方法,断言自然通过。
修复无参数方法测试的方案
你只需要改用Mockito的另一种存根语法——doReturn(...).when(mock).method(),这种方式不会提前执行目标方法,也就不会触发缓存代理的无效填充:
Mockito.doReturn("key1", "key2").when(keyService).getKey();
这样存根时不会调用代理的getKey(),缓存不会被提前污染,测试时第一次调用mock返回key1并存入缓存,第二次调用直接返回缓存的key1,断言就能正常通过。
内容的提问来源于stack exchange,提问作者vallim
相关产品推荐
相关产品推荐

