@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>)
...
..
.
此外,我没有看到任何调用 @Cacheable
、getCacheLeads(..)
方法的 LeadsService
bean 的“外部”。在你的测试中,你正在调用:
leadService.calculateLead(lead);
如下:
private void serviceIsCalled(){
Lead lead = new Lead();
lead.setLeadId("DC635EA19A39EA128764BB99052E5D1A9A");
leadService.calculateLead(lead);
}
如果 calculateLead(:Lead)
LeadService
方法正在调用 @Cacheable
、getCacheLeads(: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 中所展示的那样正确配置和使用,您的用例将正常工作,以您的域为模型。
查找说明您的配置为何会在您的情况下失败的注释。
我在 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>)
...
..
.
此外,我没有看到任何调用 @Cacheable
、getCacheLeads(..)
方法的 LeadsService
bean 的“外部”。在你的测试中,你正在调用:
leadService.calculateLead(lead);
如下:
private void serviceIsCalled(){
Lead lead = new Lead();
lead.setLeadId("DC635EA19A39EA128764BB99052E5D1A9A");
leadService.calculateLead(lead);
}
如果 calculateLead(:Lead)
LeadService
方法正在调用 @Cacheable
、getCacheLeads(: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 中所展示的那样正确配置和使用,您的用例将正常工作,以您的域为模型。
查找说明您的配置为何会在您的情况下失败的注释。