SpringBoot @Cacheable功能测试问题排查
Spring Boot缓存功能JUnit测试问题排查与解决
问题背景
在Spring Boot 2.7应用中实现了基于@Cacheable的数据库访问缓存,功能正常,但需要编写JUnit 5测试验证缓存逻辑,确保后续代码变更不会意外禁用缓存。当前编写的两种测试方式均失败:
- OPTION 1:使用
@InjectMocks直接创建AttributeDatabaseAdapter实例,缓存未生效,repository被调用3次而非预期的1次; - OPTION 2:尝试通过
@Autowired注入AttributePort,但启动时提示缺少对应Bean。
问题根源分析
OPTION 1 缓存不生效原因
@InjectMocks是Mockito提供的注解,它会直接实例化目标对象,不会经过Spring的AOP代理流程。而Spring的缓存功能是通过动态代理实现的,只有由Spring容器管理的代理对象才会触发@Cacheable的切面逻辑,直接创建的实例无法触发缓存。
OPTION 2 找不到Bean原因
测试的配置类CachingTestConfig中只注册了CacheManager和AttributeRepository的Bean,但没有将AttributeDatabaseAdapter注册为Spring Bean,因此Spring容器中不存在AttributePort的实现类实例,导致@Autowired注入失败。
修正后的测试代码
使用Spring Boot测试框架结合Mockito的@MockBean来管理Mock实例,同时让Spring容器管理目标对象的代理,确保缓存切面生效:
@ExtendWith(SpringExtension.class) @ContextConfiguration(classes = {AttributeDatabaseAdapterTest.CachingTestConfig.class}) public class AttributeDatabaseAdapterTest { @MockBean private AttributeRepository repositoryMock; @Autowired private AttributePort databaseAdapter; @EnableCaching @Configuration static class CachingTestConfig { @Bean public CacheManager cacheManager() { return new ConcurrentMapCacheManager(CacheConfig.CACHE_ATTRIBUTE); } @Bean public AttributeMapper attributeMapper() { return new AttributeMapperImpl(); } @Bean public AttributePort attributePort(AttributeRepository repository, AttributeMapper mapper) { return new AttributeDatabaseAdapter(repository, mapper); } } @BeforeEach void setUp() { reset(repositoryMock); } private final static AttributeEntity ATTR1 = AttributeEntity.builder().techId(1).build(); private final static AttributeEntity ATTR2 = AttributeEntity.builder().techId(2).build(); @Test @DisplayName("多次调用仅触发一次Repository查询") void multiple_calls_must_hit_databaseadapter_only_once() { when(repositoryMock.findAll(any(Specification.class))).thenReturn(Arrays.asList(ATTR1)); // 三次调用同一方法 assertEquals(ATTR1.getTechId(), databaseAdapter.getActiveAttributeByName("TEST", 1).getTechId()); assertEquals(ATTR1.getTechId(), databaseAdapter.getActiveAttributeByName("TEST", 1).getTechId()); assertEquals(ATTR1.getTechId(), databaseAdapter.getActiveAttributeByName("TEST", 1).getTechId()); // 验证Repository仅被调用一次 verify(repositoryMock, times(1)).findAll(any(Specification.class)); } @Test @DisplayName("Repository返回多结果时抛出异常") void filtering_Must_Return_single_Result() { when(repositoryMock.findAll(any(Specification.class))).thenReturn(Arrays.asList(ATTR1, ATTR2)); assertThrows(NonUniqueResultException.class, () -> databaseAdapter.getActiveAttributeByName("TEST", 1)); } }
关键修正点
- 使用
@MockBean替代静态@Mock:让Spring容器管理Mock实例,避免静态变量带来的生命周期问题; - 在配置类中注册
AttributeDatabaseAdapter为Bean:通过@Bean方法创建AttributePort的实现类实例,确保Spring容器可以注入; - 依赖注入
AttributePort:通过@Autowired获取Spring管理的代理对象,确保@Cacheable切面逻辑生效。
内容的提问来源于stack exchange,提问作者GonAlonso
相关产品推荐
相关产品推荐

