@Cacheable тестирование метода

У меня есть метод @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 раза?

0
0
44
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

У вас здесь происходит много всего, и кто-то, кто смотрит на это и отвечает на ваш вопрос, определенно должен читать между строк.

Во-первых, ваша конфигурация Spring даже неверна. Вы объявляете имена всех кешей, используемых вашим приложением Spring (и тестами) «статически» с использованием ConcurrentMapCacheManagerконструктор, принимающего массив имен кешей в качестве аргумента.

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.

Но затем в вашем @ServiceLeadService классе, @CacheablegetCacheLeads(: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.stackoverflow.questions.spring.cache.StaticCacheNamesIntegrationTests$Lead io.stackoverflow.questions.spring.cache.StaticCacheNamesIntegrationTests$LeadService.load(io.stackoverflow.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.stackoverflow.questions.spring.cache.StaticCacheNamesIntegrationTests$LeadService$$EnhancerBySpringCGLIB$$86664246.load(<generated>)
...
..
.

Кроме того, я не вижу ничего «вне» LeadsService bean-компонента, вызывающего @Cacheable, getCacheLeads(..) метод. Внутри вашего теста вы звоните:

leadService.calculateLead(lead);

Следующее:

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

Если метод calculateLead(:Lead)LeadService вызывает метод @Cacheable, getCacheLeads(:Lead)LeadService (внутренне), то это не приведет к срабатыванию функции кэширования, поскольку вы уже «за» настройкой прокси-сервера AOP с помощью Spring, чтобы «включить» поведение кэширования для твоя LeadService фасоль.

См. Spring Framework AOP документация по этому вопросу.

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) НЕТ ПРОКСИ, поэтому поведение кэширования Spring не будет применяться.

Только...

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

Приводит к тому, что прокси-сервер AOP украшен перехватчиками кэширования и применяется поведение кэширования.

Вы можете видеть, что ваш вариант использования будет работать правильно при правильной настройке и использовании, как показано в моем пример тестового класса, смоделированном по образцу вашего домена.

Ищите комментарии, которые объясняют, почему ваша конфигурация не будет работать в вашем случае.

Да, извините, я не позаботился о том, чтобы поставить чистый код, я изменил его, и теперь он работает. Спасибо, я просматриваю ваш комментарий -> Если метод calculateLead(:Lead) LeadService вызывает метод @Cacheable, getCacheLeads(:Lead) LeadService (внутренне), то это не приведет к срабатыванию функции кэширования поскольку вы уже «отстаете» от настройки AOP Proxy с помощью Spring, чтобы «включить» поведение кэширования для вашего bean-компонента LeadService.

sergiopf 06.04.2022 07:57

Ну и конечно ищу пример, который отлично работает.

sergiopf 06.04.2022 13:58

Рад, что помогло. Удачи!

John Blum 07.04.2022 01:43

Другие вопросы по теме