package com.ankurm.distributedcache; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import static org.assertj.core.api.Assertions.assertThat; /** * The same two behaviours, asserted identically, against whichever * CacheManager the active Spring profile wired up. That's deliberate: * the point of the Spring Cache abstraction is that ExchangeRateLookupService * and this test never change when the backend does - only the * subclass's @ActiveProfiles (and, for Redis, the container) changes. * * ExchangeRateLookupService's callCount() is a singleton counter shared * across every @Test method in a subclass's Spring context (JUnit does * not create a fresh context per test method), so each test reads the * counter before and after its own calls and asserts the *delta* rather * than an absolute value - that way test execution order inside one * class can never affect the result. */ abstract class CacheHitAndEvictionContractTest { @Autowired ExchangeRateLookupService service; abstract String backendName(); @Test void secondCallForSameKeyIsServedFromCache() { int before = service.callCount(); service.rateFor("USD/INR"); int afterFirstCall = service.callCount(); service.rateFor("USD/INR"); int afterSecondCall = service.callCount(); Transcript t = new Transcript("29-" + backendName() + "-cache-hit.txt"); t.line("backend=%s", backendName()); t.line("rateFor(\"USD/INR\") call 1 -> method body ran, callCount %d -> %d", before, afterFirstCall); t.line("rateFor(\"USD/INR\") call 2 -> served from cache, callCount stays %d", afterSecondCall); t.write(); assertThat(afterFirstCall).isEqualTo(before + 1); assertThat(afterSecondCall).isEqualTo(afterFirstCall); } @Test void evictingAKeyForcesTheNextCallToRecompute() { int before = service.callCount(); service.rateFor("USD/EUR"); int afterFirstCall = service.callCount(); service.evict("USD/EUR"); service.rateFor("USD/EUR"); int afterEvictAndRecall = service.callCount(); Transcript t = new Transcript("29-" + backendName() + "-cache-evict.txt"); t.line("backend=%s", backendName()); t.line("rateFor(\"USD/EUR\") call 1 -> method body ran, callCount %d -> %d", before, afterFirstCall); t.line("evict(\"USD/EUR\") then rateFor(\"USD/EUR\") again -> method body ran again, callCount %d -> %d", afterFirstCall, afterEvictAndRecall); t.write(); assertThat(afterFirstCall).isEqualTo(before + 1); assertThat(afterEvictAndRecall).isEqualTo(afterFirstCall + 1); } }