Now liveThe Skillselion MCP - thousands of ranked skills, loaded into your agent mid-task. No install.Get it →
giuseppe-trisciuoglio avatar

Unit Test Caching

  • 1.6k installs
  • 311 repo stars
  • Updated June 22, 2026
  • giuseppe-trisciuoglio/developer-kit

Patterns and code examples for unit testing Spring @Cacheable, @CacheEvict, @CachePut annotations using mocked repositories and in-memory CacheManager.

About

This skill teaches patterns for unit testing Spring caching annotations without full Spring context. Developers use it when verifying cache hit/miss behavior, cache key generation via SpEL expressions, eviction strategies, and conditional caching scenarios. Key workflows include configuring ConcurrentMapCacheManager, mocking repositories, using Mockito verify(mock, times(n)) to assert repository call counts, testing cache invalidation with @CacheEvict, and validating compound cache keys. Covers @Cacheable, @CachePut, @CacheEvict, plus conditional caching with unless/condition parameters.

  • Set up in-memory ConcurrentMapCacheManager for fast unit tests without Redis
  • Verify cache hits/misses by counting repository method invocations with Mockito
  • Test @CacheEvict invalidation: after eviction, verify repository called again
  • Validate SpEL compound cache keys (e.g., '#productId + - + #warehouseId')
  • Test conditional caching: unless=#result==null and condition=#id>0 scenarios

Unit Test Caching by the numbers

  • 1,632 all-time installs (skills.sh)
  • +56 installs in the week ending Jul 28, 2026 (Skillselion tracking)
  • Ranked #445 of 2,184 Testing & QA skills by installs in the Skillselion catalog
  • Security screen: MEDIUM risk (skills.sh audit)
  • Data as of Jul 28, 2026 (Skillselion catalog sync)
At a glance

unit-test-caching capabilities & compatibility

Capabilities
generate unit test boilerplate for spring cachin · mock cachemanager and repository dependencies · verify cache behavior with mockito assertions · test spel cache key expressions · validate conditional caching logic
Works with
github
Use cases
testing · debugging
Platforms
macOS · Windows · Linux
Runs
Runs locally
Pricing
Free
npx skills add https://github.com/giuseppe-trisciuoglio/developer-kit --skill unit-test-caching

Add your badge

Show developers this skill is listed on Skillselion. Paste this into your README.

Listed on Skillselion
Installs1.6k
repo stars311
Security audit3 / 3 scanners passed
Last updatedJune 22, 2026
Repositorygiuseppe-trisciuoglio/developer-kit

What it does

Write unit tests for Spring Cache annotations (@Cacheable, @CacheEvict, @CachePut) with in-memory cache managers.

Who is it for?

Developers testing backend services with Spring Cache annotations; teams building fast unit test suites without Redis/Memcached.

Skip if: Integration testing with real caches; distributed cache testing; non-Spring applications.

When should I use this skill?

Writing tests for @Cacheable methods; verifying @CacheEvict invalidation; testing @CachePut updates; validating SpEL cache keys; testing conditional caching.

What you get

Developer can write fast, isolated unit tests for cache annotations and validate caching logic before integration testing.

  • Unit test class with ConcurrentMapCacheManager configuration
  • Test cases for @Cacheable cache hit/miss scenarios
  • Test cases for @CacheEvict invalidation

By the numbers

  • Skill covers 3 main cache annotations: @Cacheable, @CacheEvict, @CachePut
  • Includes 5 full code examples with service and test classes
  • Provides troubleshooting table with 5 common issues and solutions

Files

SKILL.mdMarkdownGitHub ↗

Unit Testing Spring Caching

Overview

This skill provides patterns for unit testing Spring caching annotations (@Cacheable, @CacheEvict, @CachePut) without full Spring context. It covers cache hits/misses, invalidation, key generation, and conditional caching using in-memory ConcurrentMapCacheManager.

When to Use

  • Writing unit tests for @Cacheable method behavior
  • Verifying @CacheEvict cache invalidation works correctly
  • Testing @CachePut cache updates
  • Validating cache key generation from SpEL expressions
  • Testing conditional caching with unless/condition parameters
  • Mocking cache managers in fast unit tests without Redis

Instructions

1. Configure in-memory CacheManager: Use ConcurrentMapCacheManager for tests 2. Set up test fixtures: Mock repository and create service instance in @BeforeEach 3. Verify repository call counts: Use times(n) assertions to confirm cache behavior 4. Test cache hit: Call method twice, verify repository called once 5. Test cache miss: Verify repository called on each invocation 6. Test eviction: After @CacheEvict, verify repository called again on next read 7. Test key generation: Verify compound keys from SpEL expressions 8. Validate conditional caching: Test unless (null results) and condition (parameter-based)

Validation checkpoints:

  • Run test → If cache not working: verify @EnableCaching annotation present
  • If proxy issues: ensure method calls go through Spring proxy (no direct this calls)
  • If key mismatches: log actual cache key and compare with @Cacheable(key="...") expression

Examples

Maven

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-test</artifactId>
  <scope>test</scope>
</dependency>

Gradle

dependencies {
  implementation("org.springframework.boot:spring-boot-starter-cache")
  testImplementation("org.springframework.boot:spring-boot-starter-test")
}

Testing @Cacheable (Cache Hit/Miss)

// Service
@Service
public class UserService {
  private final UserRepository userRepository;

  public UserService(UserRepository userRepository) {
    this.userRepository = userRepository;
  }

  @Cacheable("users")
  public User getUserById(Long id) {
    return userRepository.findById(id).orElse(null);
  }
}

// Test
class UserServiceCachingTest {

  private UserRepository userRepository;
  private UserService userService;

  @BeforeEach
  void setUp() {
    userRepository = mock(UserRepository.class);
    userService = new UserService(userRepository);
  }

  @Test
  void shouldCacheUserAfterFirstCall() {
    User user = new User(1L, "Alice");
    when(userRepository.findById(1L)).thenReturn(Optional.of(user));

    // First call - hits database
    User firstCall = userService.getUserById(1L);
    // Second call - hits cache
    User secondCall = userService.getUserById(1L);

    assertThat(firstCall).isEqualTo(secondCall);
    verify(userRepository, times(1)).findById(1L); // Only once due to cache
  }

  @Test
  void shouldInvokeRepositoryOnCacheMiss() {
    when(userRepository.findById(1L)).thenReturn(Optional.of(new User(1L, "Bob")));

    userService.getUserById(1L);
    userService.getUserById(1L);

    verify(userRepository, times(2)).findById(1L); // No caching occurred
  }
}

Testing @CacheEvict

// Service
@Service
public class ProductService {
  private final ProductRepository productRepository;

  public ProductService(ProductRepository productRepository) {
    this.productRepository = productRepository;
  }

  @Cacheable("products")
  public Product getProductById(Long id) {
    return productRepository.findById(id).orElse(null);
  }

  @CacheEvict("products")
  public void deleteProduct(Long id) {
    productRepository.deleteById(id);
  }
}

// Test
class ProductCacheEvictTest {

  private ProductRepository productRepository;
  private ProductService productService;

  @BeforeEach
  void setUp() {
    productRepository = mock(ProductRepository.class);
    productService = new ProductService(productRepository);
  }

  @Test
  void shouldEvictProductFromCacheWhenDeleted() {
    Product product = new Product(1L, "Laptop", 999.99);
    when(productRepository.findById(1L)).thenReturn(Optional.of(product));

    productService.getProductById(1L); // Cache the product
    productService.deleteProduct(1L); // Evict from cache

    // Repository called again after eviction
    productService.getProductById(1L);
    verify(productRepository, times(2)).findById(1L);
  }

  @Test
  void shouldClearAllEntriesWithAllEntriesTrue() {
    Product product1 = new Product(1L, "Laptop", 999.99);
    Product product2 = new Product(2L, "Mouse", 29.99);
    when(productRepository.findById(anyLong())).thenAnswer(i ->
      Optional.of(new Product(i.getArgument(0), "Product", 10.0)));

    productService.getProductById(1L);
    productService.getProductById(2L);

    // Use reflection or clear() on ConcurrentMapCache
    productService.clearAllProducts();

    productService.getProductById(1L);
    productService.getProductById(2L);

    verify(productRepository, times(4)).findById(anyLong());
  }
}

Testing @CachePut

@Service
public class OrderService {
  private final OrderRepository orderRepository;

  public OrderService(OrderRepository orderRepository) {
    this.orderRepository = orderRepository;
  }

  @Cacheable("orders")
  public Order getOrder(Long id) {
    return orderRepository.findById(id).orElse(null);
  }

  @CachePut(value = "orders", key = "#order.id")
  public Order updateOrder(Order order) {
    return orderRepository.save(order);
  }
}

class OrderCachePutTest {

  private OrderRepository orderRepository;
  private OrderService orderService;

  @BeforeEach
  void setUp() {
    orderRepository = mock(OrderRepository.class);
    orderService = new OrderService(orderRepository);
  }

  @Test
  void shouldUpdateCacheWhenOrderIsUpdated() {
    Order original = new Order(1L, "Pending", 100.0);
    Order updated = new Order(1L, "Shipped", 100.0);

    when(orderRepository.findById(1L)).thenReturn(Optional.of(original));
    when(orderRepository.save(updated)).thenReturn(updated);

    orderService.getOrder(1L);
    orderService.updateOrder(updated);

    // Next call returns updated version from cache
    Order cachedOrder = orderService.getOrder(1L);
    assertThat(cachedOrder.getStatus()).isEqualTo("Shipped");
  }
}

Testing Conditional Caching

@Service
public class DataService {
  private final DataRepository dataRepository;

  public DataService(DataRepository dataRepository) {
    this.dataRepository = dataRepository;
  }

  // Don't cache null results
  @Cacheable(value = "data", unless = "#result == null")
  public Data getData(Long id) {
    return dataRepository.findById(id).orElse(null);
  }

  // Only cache when id > 0
  @Cacheable(value = "users", condition = "#id > 0")
  public User getUser(Long id) {
    return dataRepository.findById(id).map(u -> new User(u.getId(), u.getName())).orElse(null);
  }
}

class ConditionalCachingTest {

  @Test
  void shouldNotCacheNullResults() {
    DataRepository dataRepository = mock(DataRepository.class);
    when(dataRepository.findById(999L)).thenReturn(Optional.empty());
    DataService service = new DataService(dataRepository);

    service.getData(999L);
    service.getData(999L);

    verify(dataRepository, times(2)).findById(999L); // Called twice - no caching
  }

  @Test
  void shouldNotCacheWhenConditionIsFalse() {
    DataRepository dataRepository = mock(DataRepository.class);
    when(dataRepository.findById(-1L)).thenReturn(Optional.of(new Data(-1L, "Test")));

    DataService service = new DataService(dataRepository);

    service.getUser(-1L);
    service.getUser(-1L);

    verify(dataRepository, times(2)).findById(-1L); // Condition "#id > 0" = false
  }
}

Testing Cache Keys with SpEL

@Service
public class InventoryService {
  private final InventoryRepository inventoryRepository;

  public InventoryService(InventoryRepository inventoryRepository) {
    this.inventoryRepository = inventoryRepository;
  }

  // Compound key: productId-warehouseId
  @Cacheable(value = "inventory", key = "#productId + '-' + #warehouseId")
  public InventoryItem getInventory(Long productId, Long warehouseId) {
    return inventoryRepository.findByProductAndWarehouse(productId, warehouseId);
  }
}

class CacheKeyTest {

  @Test
  void shouldUseCorrectCacheKeyForDifferentCombinations() {
    InventoryRepository repository = mock(InventoryRepository.class);
    InventoryItem item = new InventoryItem(1L, 1L, 100);
    when(repository.findByProductAndWarehouse(1L, 1L)).thenReturn(item);

    InventoryService service = new InventoryService(repository);

    // Same key: "1-1" - should cache
    service.getInventory(1L, 1L);
    service.getInventory(1L, 1L); // Cache hit
    verify(repository, times(1)).findByProductAndWarehouse(1L, 1L);

    // Different key: "2-1" - cache miss
    service.getInventory(2L, 1L); // Cache miss
    verify(repository, times(2)).findByProductAndWarehouse(any(), any());
  }
}

Best Practices

  • Mock repository calls: Use verify(mock, times(n)) to assert cache behavior
  • Test both hit and miss scenarios: Don't just test the happy path
  • Clear cache state: Reset between tests to avoid flaky results
  • Use `ConcurrentMapCacheManager`: Fast, no external dependencies
  • Verify eviction: Always test that @CacheEvict actually invalidates cached data

Constraints and Warnings

  • `@Cacheable` requires proxy: Direct method calls (this.method()) bypass caching - use dependency injection
  • Cache key collisions: Compound keys from SpEL must be unique per dataset
  • Null caching: Null results are cached by default - use unless = "#result == null" to exclude
  • `@CachePut` always executes: Unlike @Cacheable, it always runs the method
  • Memory usage: In-memory caches grow unbounded - consider TTL for long-running tests
  • Thread safety: ConcurrentMapCacheManager is thread-safe; distributed caches may require additional config

Troubleshooting

IssueSolution
Cache not workingVerify @EnableCaching on test config
Proxy bypassUse autowired/constructor injection, not direct this calls
Key mismatchLog cache key with cache.getNativeKey() to debug SpEL
Flaky testsClear cache in @BeforeEach before each test

References

Related skills

Forks & variants (1)

Unit Test Caching has 1 known copy in the catalog totaling 21 installs. They canonicalize to this original listing.

How it compares

Use unit-test-caching for fast mocked cache unit tests; use integration skills when real cache backends must be exercised end-to-end.

FAQ

Why doesn't caching work in my test?

Ensure @EnableCaching is on your test config class. Also verify method calls go through Spring proxy (use dependency injection, not direct this.method() calls).

How do I verify the repository was called exactly once?

Use Mockito: verify(userRepository, times(1)).findById(1L). If times(1), cache hit. If times(2), cache miss.

Should I cache null results?

By default, Spring caches null. Use @Cacheable(unless="#result == null") to exclude nulls from cache.

Is Unit Test Caching safe to install?

skills.sh reports 3 of 3 security scanners passed. Review the Security Audits panel on this page before installing in production.

Testing & QAtestingbackend

This week in AI coding

Five minutes, every Monday - the tools, releases and tactics for developers.

unsubscribe anytime.