@Cacheable 测试方法

@Cacheable testing over method

我在 class 中有一个 @Cacheable 方法。 我尝试在第一次调用该方法后创建该缓存,然后,第二次调用不应进入方法 getCacheLeads.

@Service
public class LeadService {

    @Autowired
    private LeadRepository leadRepository;

    @Autowired
    public LeadService(LeadRepository leadRepository) {
        this.leadRepository = leadRepository;
    }

    public void calculateLead(Lead leadBean) {
        Lead lead = this.getCacheLeads(leadBean);
    }

    @Cacheable(cacheNames="leads", key="#leadBean.leadId")
    public Lead getCacheLeads(Lead leadBean){
        Lead result = leadRepository.findByLeadId(leadBean.getLeadId());
        ***logic to transform de Lead object***
        return result;
    }
}

但是在测试期间从未使用缓存,使用相同的参数调用它两次 (serviceIsCalled) 以确保它被调用两次以检查它。

@ExtendWith(SpringExtension.class)
public class LeadServiceTest {

    private LeadService leadService;
    
    @Mock
    private LeadRepository leadRepository;
    
    @Autowired 
    CacheManager cacheManager;
    
    @BeforeEach
    public void setUp(){
        leadService = new LeadService(leadRepository);
    }

    @Configuration
    @EnableCaching
    static class Config {
        @Bean
        CacheManager cacheManager() {
            return new ConcurrentMapCacheManager("leads"); 
        }
    }
    
    @Test
    public void testLead(){
        givenData();
        serviceIsCalled();
        serviceIsCalled();
        checkDataArray();
    }
    
    private void givenData() {
        Lead lead = new Lead();
        lead.setLeadId("DC635EA19A39EA128764BB99052E5D1A9A");
        
        Mockito.when(leadRepository.findByLeadId(any()))
        .thenReturn(lead);
    }
    
    private void serviceIsCalled(){
        Lead lead = new Lead();
        lead.setLeadId("DC635EA19A39EA128764BB99052E5D1A9A");
        leadService.calculateLead(lead);
    }
    
    private void checkDataArray(){
        verify(leadRepository, times(1)).findByLeadId(anyString());
    }
}

为什么调用了2次?

你这里发生了很多事情,看到这个并回答你问题的人肯定会读懂字里行间。

首先,您的 Spring 配置甚至都不正确。您正在使用 ConcurrentMapCacheManager constructor 接受缓存名称数组作为参数来“静态”声明 Spring 应用程序(和测试)使用的所有缓存的名称。

NOTE: Caches identified explicitly by name, and only these caches, are available at runtime.

@Bean
CacheManager cacheManager() {
    return new ConcurrentMapCacheManager("LEAD_DATA"); 
}

在这种情况下,您的第一个也是唯一的缓存名为“LEAD_DATA”。

NOTE: Only the no arg constructor in `ConcurrentMapCacheManager allows dynamically created caches by name at runtime.

但是,在您的 @Service LeadService class、@Cacheable getCacheLeads(:Lead) 方法中,您将缓存声明为“线索”。

@Service
public class LeadService {

    @Cacheable(cacheNames="leads", key="#leadBean.leadId")
    public Lead getCacheLeads(Lead leadBean){
        // ...
    }
}

这种错误配置实际上会在运行时导致类似于以下内容的异常:

java.lang.IllegalArgumentException: Cannot find cache named 'leads' for Builder[public io.Whosebug.questions.spring.cache.StaticCacheNamesIntegrationTests$Lead io.Whosebug.questions.spring.cache.StaticCacheNamesIntegrationTests$LeadService.load(io.Whosebug.questions.spring.cache.StaticCacheNamesIntegrationTests$Lead)] caches=[leads] | key='#lead.id' | keyGenerator='' | cacheManager='' | cacheResolver='' | condition='' | unless='' | sync='false'

    at org.springframework.cache.interceptor.AbstractCacheResolver.resolveCaches(AbstractCacheResolver.java:92)
    at org.springframework.cache.interceptor.CacheAspectSupport.getCaches(CacheAspectSupport.java:252)
    at org.springframework.cache.interceptor.CacheAspectSupport$CacheOperationContext.<init>(CacheAspectSupport.java:724)
    at org.springframework.cache.interceptor.CacheAspectSupport.getOperationContext(CacheAspectSupport.java:265)
    at org.springframework.cache.interceptor.CacheAspectSupport$CacheOperationContexts.<init>(CacheAspectSupport.java:615)
    at org.springframework.cache.interceptor.CacheAspectSupport.execute(CacheAspectSupport.java:345)
    at org.springframework.cache.interceptor.CacheInterceptor.invoke(CacheInterceptor.java:64)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
    at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:753)
    at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:698)
    at io.Whosebug.questions.spring.cache.StaticCacheNamesIntegrationTests$LeadService$$EnhancerBySpringCGLIB$664246.load(<generated>)
...
..
.

此外,我没有看到任何调用 @CacheablegetCacheLeads(..) 方法的 LeadsService bean 的“外部”。在你的测试中,你正在调用:

leadService.calculateLead(lead);

如下:

private void serviceIsCalled(){
    Lead lead = new Lead();
    lead.setLeadId("DC635EA19A39EA128764BB99052E5D1A9A");
    leadService.calculateLead(lead);
}

如果 calculateLead(:Lead) LeadService 方法正在调用 @CacheablegetCacheLeads(:Lead) LeadService 方法(内部),则不会导致缓存功能启动,因为您已经“落后于”Spring 设置的 AOP 代理,以便为您的 LeadService bean“启用”缓存行为。

请参阅 Spring Framework AOP documentation 关于此事。

NOTE: Spring's Cache Abstraction, like the Spring's Transaction Management, is built on the Spring AOP infrastructure, as are many other things in Spring.

在您的情况下,这意味着:

Test -> <PROXY> -> LeadService.calculateLead(:Lead) -> LeadService.getCacheLeads(:Lead)

但是,在 LeadSevice.calculateLead(:Lead)LeadService.getCacheLeads(:Lead) 之间,不涉及 PROXY,因此不会应用 Spring 的缓存行为。

只有...

Test (or some other bean) -> <PROXY> -> LeadService.getCacheLeads(:Lead)

将导致调用缓存拦截器装饰的 AOP 代理并应用缓存行为。

您可以看到,如果按照我的 example test class 中所展示的那样正确配置和使用,您的用例将正常工作,以您的域为模型。

查找说明您的配置为何会在您的情况下失败的注释。