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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.05 16:15:12