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

Spring Boot 2:无参@Cacheable方法Mockito测试失效问题

问题原因分析:无参数@Cacheable方法测试失败的核心原因

这个问题的本质是Mockito存根逻辑与Spring Cache代理的交互差异,具体来说是无参数方法的存根方式触发了缓存代理的提前执行,导致缓存被无效值填充,进而破坏了测试流程。

为什么无参数方法的测试会失败?

先看你无参数方法的存根代码:

Mockito.when(keyService.getKey()).thenReturn("key1", "key2");

Mockito的这种存根方式(when(obj.method()))有个关键特性:它会先实际执行一次目标方法,再完成存根配置。结合Spring Cache代理的逻辑,整个流程变成了这样:

  1. 执行when(keyService.getKey())时,会调用Spring缓存代理的getKey()方法。
  2. 缓存代理检查缓存,此时缓存为空,于是调用底层Mockito mock对象的getKey()——但这时候存根还没设置,mock会返回默认值(String类型就是null)。
  3. 缓存代理把这个null存入了keyCache缓存,对应的键是Spring Cache默认的无参数键SimpleKey.EMPTY。
  4. 直到这一步,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缓存代理的拦截逻辑,也就不会提前向缓存里存入无效值。

测试执行流程就完全符合预期了:

  1. 第一次调用keyService.getKey("xyz"):缓存代理检查缓存为空,调用mock方法返回key1,并把key1存入缓存(键对应参数"xyz")。
  2. 第二次调用keyService.getKey("xyz"):缓存代理直接从缓存取出key1返回,不会再调用mock方法,断言自然通过。

修复无参数方法测试的方案

你只需要改用Mockito的另一种存根语法——doReturn(...).when(mock).method(),这种方式不会提前执行目标方法,也就不会触发缓存代理的无效填充:

Mockito.doReturn("key1", "key2").when(keyService).getKey();

这样存根时不会调用代理的getKey(),缓存不会被提前污染,测试时第一次调用mock返回key1并存入缓存,第二次调用直接返回缓存的key1,断言就能正常通过。

内容的提问来源于stack exchange,提问作者vallim

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.28 10:19:04