How to test @Cacheable?

Viewed 29676

I am struggling with testing @Cacheable within a Spring Boot Integration Test. This is my second day learning how to do Integration Tests and all of the examples I have found use older versions. I also saw an example of assetEquals("some value", is()) but nothing with an import statement to know which dependency "is" belongs to. The test fails at the second

This is my integration test....

@RunWith(SpringRunner.class)
@DataJpaTest // used for other methods
@SpringBootTest(classes = TestApplication.class)
@SqlGroup({
        @Sql(executionPhase = ExecutionPhase.BEFORE_TEST_METHOD,
                scripts = "classpath:data/Setting.sql") })
public class SettingRepositoryIT {

    @Mock
    private SettingRepository settingRepository;

    @Autowired
    private Cache applicationCache;


    @Test
    public void testCachedMethodInvocation() {
        List<Setting> firstList = new ArrayList<>();
        Setting settingOne = new Setting();
        settingOne.setKey("first");
        settingOne.setValue("method invocation");
        firstList.add(settingOne);

        List<Setting> secondList = new ArrayList<>();
        Setting settingTwo = new Setting();
        settingTwo.setKey("second");
        settingTwo.setValue("method invocation");
        secondList.add(settingTwo);

        // Set up the mock to return *different* objects for the first and second call
        Mockito.when(settingRepository.findAllFeaturedFragrances()).thenReturn(firstList, secondList);

        // First invocation returns object returned by the method
        List<Setting> result = settingRepository.findAllFeaturedFragrances();
        assertEquals("first", result.get(0).getKey());

        // Second invocation should return cached value, *not* second (as set up above)
        List<Setting> resultTwo = settingRepository.findAllFeaturedFragrances();
        assertEquals("first", resultTwo.get(0).getKey()); // test fails here as the actual is "second."

        // Verify repository method was invoked once
        Mockito.verify(settingRepository, Mockito.times(1)).findAllFeaturedFragrances();
        assertNotNull(applicationCache.get("findAllFeaturedFragrances"));

        // Third invocation with different key is triggers the second invocation of the repo method
        List<Setting> resultThree = settingRepository.findAllFeaturedFragrances();
        assertEquals(resultThree.get(0).getKey(), "second");
    }
}

ApplicationContext, components, entities, repositories and service layer for tests. The reason why I do it this way is because this maven module is used in other modules as a dependency.

@ComponentScan({ "com.persistence_common.config", "com.persistence_common.services" })
@EntityScan(basePackages = { "com.persistence_common.entities" })
@EnableJpaRepositories(basePackages = { "com.persistence_common.repositories" })
@SpringBootApplication
public class TestApplication {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

Cache config....

@Configuration
@EnableCaching
public class CacheConfig {

    public static final String APPLICATION_CACHE = "applicationCache";

    @Bean
    public FilterRegistrationBean registerOpenSessionInViewFilterBean() {
        FilterRegistrationBean registrationBean = new FilterRegistrationBean();
        OpenEntityManagerInViewFilter filter = new OpenEntityManagerInViewFilter();
        registrationBean.setFilter(filter);
        registrationBean.setOrder(5);
        return registrationBean;
    }


    @Bean
    public Cache applicationCache() {
        return new GuavaCache(APPLICATION_CACHE, CacheBuilder.newBuilder()
                .expireAfterWrite(30, TimeUnit.DAYS)
                .build());
    }
}

The repository under test....

public interface SettingRepository extends JpaRepository<Setting, Integer> {

    @Query(nativeQuery = true, value = "SELECT * FROM Setting WHERE name = 'featured_fragrance'")
    @Cacheable(value = CacheConfig.APPLICATION_CACHE, key = "#root.methodName")
    List<Setting> findAllFeaturedFragrances();
}
2 Answers

In my case I wanted to validate the expression in the unless expression in the @Cacheable annotation, so I think it makes perfect sense and I'm not testing Spring's code.

I managed to test it without using Spring Boot, so it is plain Spring test:

@RunWith(SpringRunner.class)
@ContextConfiguration
public class MyTest {

    private static MyCacheableInterface myCacheableInterfaceMock = mock(MyCacheableInterface.class);

    @Configuration
    @EnableCaching
    static class Config {

        @Bean
        public MyCacheableInterface myCacheableInterface() {
            return myCacheableInterfaceMock;
        }

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

    @Autowired
    private MyCacheableInterface myCacheableInterface;

    @Test
    public void test() {
        when(myCacheableInterfaceMock.businessMethod(anyString())).then(i -> {
            List<MyObject> list = new ArrayList<>();
            list.add(new MyObject(new Result("Y")));
            return list;
        });
        myCacheableInterface.businessMethod("test");
        verify(myCacheableInterfaceMock).businessMethod(anyString());
        myCacheableInterface.businessMethod("test");
        verifyNoMoreInteractions(myCacheableInterfaceMock);
    }
}

In MyCacheableInterface I have the following annotation:

public interface MyCacheableInterface {
    @Cacheable(value = "myObject", unless = "#result.?[Result.getSuccess() != 'Y'].size() == #result.size()")
    List<MyObject> businessMethod(String authorization);
}
Related