Free tools Windows power users keep installed

One-click scans. No signup required.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

To verify Spring’s cache annotations, run an integration test against the Spring-managed bean, call the same method twice with the same key, and prove that the underlying operation ran only once. Then call a different key to confirm entries are separated. This tests application method-result caching; it does not test Spring TestContext’s separate ApplicationContext reuse mechanism.

Two caches that are easy to confuse

Application caching

Spring’s cache abstraction stores method results. With @Cacheable, a matching key can return a previously stored result without invoking the method again. @CachePut always invokes the method and places its result in the cache, while @CacheEvict removes entries. The annotation processor handles these annotations by creating an interception proxy around the Spring bean. See the Spring cache annotation reference and the official caching guide.

Spring TestContext caching

Spring’s TestContext framework can reuse an ApplicationContext between tests whose configuration produces the same context-cache key. That speeds setup; it does not demonstrate that a cached service method returned a stored result. The static TestContext cache defaults to a maximum of 32 contexts and evicts the least-recently-used entry when full. Its behavior is documented in Context Caching.

Concern What is reused What a test proves
Application cache A method result under a cache key Annotation wiring, key behavior, and selected eviction/update semantics
TestContext cache A configured Spring ApplicationContext Only that test setup can be reused; it says nothing about @Cacheable hits

Choose an integration-test boundary

A unit test that constructs the target with new bypasses Spring’s proxy, so cache annotations will not run. Use a context-backed test that injects the bean from Spring. Spring Boot’s testing documentation describes this approach as loading an ApplicationContext without deploying the application or connecting to every production system; most projects use spring-boot-starter-test. Check the dependency coordinates for your Boot line in the Spring Boot testing overview and test-module reference. The current Boot 4.1.1 reference lists spring-boot-cache-test; do not copy that module name into an older project without checking its corresponding documentation.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

A focused @Cacheable integration test

The following pattern makes the underlying work observable with a counting collaborator. The exact cache provider and build coordinates are project choices; the important points are enabling caching, injecting the Spring bean, and asserting both results and invocation count.

 @SpringBootTest
 class PricingServiceCacheIT {

   @Autowired PricingService pricingService;
   @Autowired CountingPriceSource priceSource;

   @BeforeEach
   void clearState() {
     priceSource.reset();
     // Clear the relevant cache here if your provider retains entries
   }

   @Test
   void sameKeyUsesOneUnderlyingCall() {
     Price first = pricingService.priceFor("SKU-42");
     Price second = pricingService.priceFor("SKU-42");

     assertThat(second).isEqualTo(first);
     assertThat(priceSource.callsFor("SKU-42")).isEqualTo(1);
   }

   @Test
   void differentKeysUseDifferentEntries() {
     pricingService.priceFor("SKU-42");
     pricingService.priceFor("SKU-99");

     assertThat(priceSource.callsFor("SKU-42")).isEqualTo(1);
     assertThat(priceSource.callsFor("SKU-99")).isEqualTo(1);
   }
 }

The production bean must be annotated and managed by Spring:

 @Service
 public class PricingService {
   private final CountingPriceSource source;

   public PricingService(CountingPriceSource source) {
     this.source = source;
   }

   @Cacheable(cacheNames = "prices", key = "#sku")
   public Price priceFor(String sku) {
     return source.load(sku);
   }
 }

Enable caching in the application or test configuration, for example with @EnableCaching. Inject PricingService; do not instantiate it directly. A call made from another method inside the same object can also bypass the proxy through self-invocation, so arrange the test around an external call to the bean.

Make isolation explicit

  • Use a unique key per assertion or clear the relevant cache before each test.
  • Reset counters and mutable fixtures in @BeforeEach.
  • Ensure the key expression matches the arguments you vary; otherwise two apparently different calls may intentionally map to one key.
  • Do not rely on a previously warm context to establish a cache hit. Context reuse and cache contents are separate state.

Testing eviction and updates

@CacheEvict

Test the observable consequence: load a value, evict it, then call again and assert that the underlying source runs a second time.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
 @Test
 void evictionForcesReload() {
   pricingService.priceFor("SKU-42");
   pricingService.removeCachedPrice("SKU-42");
   pricingService.priceFor("SKU-42");

   assertThat(priceSource.callsFor("SKU-42")).isEqualTo(2);
 }

@CachePut

Because @CachePut does not skip the method, assert that the update executes and that a later cached read observes the updated value.

 @Test
 void updateExecutesAndRefreshesCache() {
   pricingService.priceFor("SKU-42");
   pricingService.updatePrice("SKU-42", new Price("12.00"));

   Price result = pricingService.priceFor("SKU-42");

   assertThat(result.amount()).isEqualTo("12.00");
   assertThat(priceSource.updateCalls()).isEqualTo(1);
 }

These examples express the semantics documented by Spring; adapt names and assertions to your service and provider.

Pick a backing cache that matches the behavior under test

Spring supplies the abstraction, not a universal storage engine. The selected implementation owns storage details, concurrency, expiry, serialization, eviction policy, and multi-process behavior. Spring states that the abstraction has “no special handling for multi-threaded and multi-process environments, as such features are handled by the cache implementation.” See Understanding the Cache Abstraction.

Test setup Covers well Does not establish Use when
Simple in-memory cache Proxy wiring, key selection, repeated-call behavior, basic eviction/update assertions Redis or JCache serialization, cluster invalidation, production expiry and multi-node races You need a fast framework-level integration check
Production provider in a realistic environment Provider-specific expiry, eviction, serialization, locking, invalidation, and process boundaries Nothing outside the configured provider and environment Those provider behaviors are part of the requirement

An in-memory test is valuable but deliberately narrow. If production uses Redis, Caffeine policies, JCache, or another backend, add provider-backed tests for the features your application depends on. Do not describe a passing map-backed test as proof that distributed cache behavior works.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Diagnose a failing cache assertion

The underlying method runs twice for the same key

  • Confirm the test injected the Spring bean rather than calling new PricingService(...).
  • Confirm caching is enabled and the cache name is configured.
  • Check for self-invocation: an internal call on this does not pass through the proxy.
  • Verify the key expression and argument equality.
  • Clear stale entries and reset test fixtures so a previous test cannot mask the result.

The test suite is unexpectedly slow

Compare context configurations across classes. The TestContext cache key includes configuration classes, active profiles, property sources, context customizers, and parent context. Small differences can force new contexts. Running tests in separate processes also clears the static cache, so reuse cannot cross that boundary. Enable debug logging for org.springframework.test.context.cache to inspect cache statistics.

@DirtiesContext removes and rebuilds a context when it has been corrupted or must be reloaded; it is not a routine replacement for clearing application-cache entries before every method. Prefer targeted cache clearing or isolated keys when only application cache state is involved.

Version and dependency notes

Spring Framework references currently show stable lines 7.0.9 and 6.2.19, while the Spring Boot test-module reference currently shows Boot 4.1.1 and stable lines 4.0.8, 3.5.16, 3.4.13, and 3.3.13. These labels change; verify the documentation matching your project before selecting dependencies. The general Boot test-module page distinguishes the broad spring-boot-starter-test route from focused feature modules.

What a trustworthy result looks like

  • The test calls a Spring-managed proxy.
  • Repeated identical arguments return equivalent results while the underlying operation is counted once.
  • A different key causes its own underlying operation.
  • Eviction and update tests assert the documented postconditions when those annotations are used.
  • Provider-specific requirements run against the relevant provider, not only an in-memory substitute.
  • Application-cache assertions are kept conceptually separate from TestContext startup reuse.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.