Spring Cache测试中缓存未填充问题排查求助
问题描述
我编写了如下测试用例,用于断言数据已存入Spring Cache:
@Slf4j @ExtendWith(SpringExtension.class) @SpringBootTest(classes = MainSpringApplication.class) class NamesServiceCacheTest { @Mock ApiClient apiClientMock; NamesServiceImpl sut; @Autowired private CacheManager cacheManager; @BeforeEach void setUp() { sut = new NamesServiceImpl(apiClientMock); } @Configuration @EnableCaching static class Config { @Bean public CacheManager cacheManager() { SimpleCacheManager cacheManager = new SimpleCacheManager(); List<Cache> caches = new ArrayList<>(); caches.add(new ConcurrentMapCache("namesCache")); cacheManager.setCaches(caches); return cacheManager; } } @Test void testResponseGoesIntoCache() { when(apiClientMock.getNamesFromXApi()).thenReturn(getMockedResponse()); sut.getNames(); var cache = cacheManager.getCache("namesCache"); // 调试时此处缓存为空 assertEquals(getMockedResponse(), cache.get("namesCache").get()); }
但测试中无数据存入缓存。怀疑是CacheManager未在Spring上下文正确加载,或是配置类继承CachingConfigurerSupport导致问题。
全局缓存配置代码:
@Configuration @EnableCaching public class CachingConfig extends CachingConfigurerSupport { @Bean public CacheManager cacheManager() { SimpleCacheManager cacheManager = new SimpleCacheManager(); List<Cache> caches = new ArrayList<>(); caches.add(new ConcurrentMapCache("namesCache")); cacheManager.setCaches(caches); return cacheManager; } }
服务类代码:
@Slf4j @Service @RequiredArgsConstructor public class NamesServiceImpl implements NameService { private final ApiClient apiClient; @Override @Cacheable("namesCache") public Mono<GetInstitutionsResponse> getNames() { return apiClient.getNamesFromXApi(); } }
核心原因分析
1. 手动实例化的服务类无Spring代理,缓存注解不生效
setUp里直接new NamesServiceImpl(apiClientMock)创建的实例,不属于Spring容器托管对象。Spring的@Cacheable依赖AOP代理实现缓存增强,只有Spring上下文管理的Bean才会触发缓存逻辑,手动new的对象不会走缓存切面。
2. Reactor Mono未订阅,缓存逻辑未执行
getNames()返回Mono异步类型,Reactor数据流只有在被订阅(比如调用subscribe()、block())时才会执行。测试里仅调用sut.getNames()但未订阅,导致API调用和缓存逻辑都没触发,自然没有数据存入缓存。
3. 缓存Key使用错误
测试中用cache.get("namesCache")获取缓存,但@Cacheable默认Key规则是:无参数方法的默认Key为SimpleKey.EMPTY,而非缓存名称"namesCache"。就算缓存生效,用这个Key也取不到数据。
4. 测试内部配置与全局配置冲突
测试类内部定义了Config类,同时项目存在全局CachingConfig,可能导致Spring加载多个CacheManager实例。服务类实际绑定的是全局配置的CacheManager,但测试注入的是内部配置的实例,导致检查的缓存容器没有数据。
修复方案
1. 让Spring管理服务类实例
弃用手动new对象,改用@MockBean替换Spring容器中的ApiClient,直接注入NamesServiceImpl:
@Slf4j @ExtendWith(SpringExtension.class) @SpringBootTest(classes = MainSpringApplication.class) class NamesServiceCacheTest { @MockBean ApiClient apiClientMock; @Autowired NamesServiceImpl sut; @Autowired private CacheManager cacheManager; // 移除手动实例化的setUp方法 @Test void testResponseGoesIntoCache() { when(apiClientMock.getNamesFromXApi()).thenReturn(getMockedResponse()); // 订阅Mono触发流程执行 sut.getNames().block(); // 使用正确的Key获取缓存 var cacheValue = cacheManager.getCache("namesCache").get(SimpleKey.EMPTY).get(); assertEquals(getMockedResponse(), cacheValue); } }
2. 确保Mono被订阅
测试中调用block()(适合测试场景,阻塞直到异步流程完成)或subscribe()触发数据流执行:
sut.getNames().block();
3. 使用正确的缓存Key
要么遵循默认规则用SimpleKey.EMPTY取值,要么给@Cacheable指定明确的Key:
// 服务类指定自定义Key @Cacheable(value = "namesCache", key = "'all-names'") public Mono<GetInstitutionsResponse> getNames() { return apiClient.getNamesFromXApi(); } // 测试用对应Key获取 var cacheValue = cacheManager.getCache("namesCache").get("all-names").get();
4. 统一缓存配置
移除测试类内部的Config,直接使用全局CachingConfig;或者将测试内部配置改为@TestConfiguration,避免与全局配置冲突:
// 测试内部配置改为TestConfiguration,仅在当前测试生效 @TestConfiguration @EnableCaching static class Config { @Bean public CacheManager cacheManager() { // 缓存配置逻辑不变 } }
内容的提问来源于stack exchange,提问作者Kaigo

