Using caches in a Spring application is relatively easy. It makes the overall performance better, but opens some exciting problems when it comes to integration testing the application. Adding caching behavior to a functionality without properly thinking over the consequences, is one of the biggest mistakes, a beginner programmer can make. You must have full understanding and control, how your application works with the selected cache implementation, and you must be able to test it.
Also your unit test cases must be namely independent from each other, and should not depend on the order of the methods being executed.
Unfortunately when you execute the integration test with @SpringRunner, the Spring environment will be started only once, therefore the caches might contain data from previous test executions in case of subsequent calls.
In the common Spring integration test scenario, the test method or class uses some predefined data from memory database. While Repository classes are perfect candidate for caching, data from previous test execution might remain in the cache even if you carefully drop and recreate all tables in your memory database.
Localizing such problems can be really complicated, while your method or even your class runs without problem, when you start it without other test cases.
In order for make your application eligible for integration testing, you have the following possibilities. You can choose the one that fits to your integration test requirements.
Use Spring Boot Dev Tools
I generally recommend to use Spring Boot Dev Tools. It makes the whole development process way faster. It also turns caching off automatically during the development phase.
Unfortunately it is not so easy to enable and disable Dev Tools according to your needs for given test classes, to test your application with and without caching. It requires to overwrite caching setting in your configuration file for the corresponding profile, where you do want to enable caching. You need to use also multiple integration test profiles, which is bad for the general maintainability.
Turning off cache for integration test profile
It is possible to turn off caching in the configuration file for a given profile in the configuration file using
spring.cache.type=NONE
Another solution with the same result is to use a mock implementation of Spring caching for the integration test profile. Spring does already have such implementation out of the box, called NoOpCacheManager. You need to define the cache manager in the configuration, like this:
/** * Disabling cache for integration test */ @Bean public CacheManager cacheManager() { return new NoOpCacheManager(); }
Both methods have the same disadvantage as using Dev Tools. Either caching can not be tested at all during integration test, or you need to define a multiple profiles for integration tests with caching and without caching.
Evict caches manually
In order to get full control over testing your application with caching, I recommend using a single integration test profile, with caching turned on, and handling the cache eviction manually. It makes your test structure simpler, and allows you to test non obvious cases as well.
- What if your call gets the result from cache?
- What if the cache is empty?
- What if the cache is full with other entities and your result gets removed from the cache?
- Does cache eviction work by data insert, update or delete, as you expected?
- Have you defined to cache null values as well?
- Did you define your key for caching correctly?
All the above scenarios can be covered by playing with the CacheManager and Cache interfaces.
To get a clean environment before each test execution, you should evict all caches before running an integration test function. The following method iterates through all Spring caches and evicts them.
@Autowired private CacheManager cacheManager; public void evictAllCaches(){ for(String name : cacheManager.getCacheNames()){ cacheManager.getCache(name).clear(); } }
You need to call this method every time you tests a function that uses caching. You can do it manually as the first command in your test method, or obviously also in the test initialization phase, aka in the method annotated with @Before.
The risk of this solution is, that you will forget it. Therefore I find it a good habit to implement an abstract parent class for all cache related test, define the evictAllCaches method in it, and annotate with @Before. This way you also have the possibility to find all cache related classes fast, using the class hierarchy. You can also see, which classes should be able to work independent from caching, which is also a very important aspect.