如何获取Caffeine缓存中键的不可变快照以完成测试断言
Caffeine缓存键快照获取与过期测试失败解决
问题场景
我用Caffeine缓存实现了一个简单的注册表:
class LocalFactorsRegistry( duration: Duration, ) : FactorsRegistry { private val factors = Caffeine .newBuilder() .expireAfterWrite(duration) .build<String, Boolean>() override fun getUsedFactors(): Set<String> = factors.asMap().keys override fun markFactorAsUsed(factorId: String) { factors.put(factorId, true) } }
为测试缓存项是否会被驱逐,我编写了如下测试代码:
@Test fun `should mark factor as used and wait for expiration`() { val factory = LocalFactorsRegistry(Duration.ofMillis(100)) factory.getUsedFactors() shouldBe emptySet() factory.markFactorAsUsed("factor2") Thread.sleep(100) factory.getUsedFactors() shouldBe emptySet() }
但测试在最后一个断言环节失败,抛出异常:
java.util.NoSuchElementException at com.github.benmanes.caffeine.cache.BoundedLocalCache$EntryIterator.nextKey(BoundedLocalCache.java:3754) at com.github.benmanes.caffeine.cache.BoundedLocalCache$KeyIterator.next(BoundedLocalCache.java:3350) at kotlin.collections.CollectionsKt___CollectionsKt.toList(_Collections.kt:1323) at io.kotest.assertions.print.IterablePrint.print(IterablePrint.kt:6) at io.kotest.assertions.print.IterablePrint.print(IterablePrint.kt:3) at io.kotest.assertions.print.PrintKt.print(Print.kt:48) at io.kotest.assertions.eq.IterableEq.generateError(IterableEq.kt:221) at io.kotest.assertions.eq.IterableEq.checkSetEquality(IterableEq.kt:77) at io.kotest.assertions.eq.IterableEq.equals(IterableEq.kt:65) at io.kotest.assertions.eq.EqKt.eq(Eq.kt:51) at io.kotest.assertions.eq.EqKt.eq(Eq.kt:66) at io.kotest.matchers.ShouldKt$equalityMatcher$1.test(should.kt:73) at io.kotest.matchers.ShouldKt.invokeMatcher(should.kt:38) at io.kotest.matchers.ShouldKt.should(should.kt:33) at io.kotest.matchers.ShouldKt.shouldBe(should.kt:17) at com.example.LocalFactorsRegistryTest.should mark factor as used and wait for expiration(AbstractFactorsRegistryTest.kt:35) at java.base/java.lang.reflect.Method.invoke(Method.java:580) at java.base/java.util.ArrayList.forEach(ArrayList.java:1596) at java.base/java.util.ArrayList.forEach(ArrayList.java:1596)
我需要知道如何获取Caffeine缓存中键的不可变快照,以正确完成断言。
解决方案
1. 生成不可变快照的正确方式
问题根源在于factors.asMap().keys返回的是缓存的实时视图,而非独立快照。当Kotest遍历这个视图进行断言时,缓存的过期清理操作可能刚好在遍历中途执行,导致迭代器抛出NoSuchElementException。
要获取不可变快照,只需将实时视图的键集合复制到新的不可变集合中,修改getUsedFactors方法:
// 方式1:用Java HashSet复制生成快照 override fun getUsedFactors(): Set<String> = HashSet(factors.asMap().keys) // 方式2:用Kotlin扩展函数生成不可变Set override fun getUsedFactors(): Set<String> = factors.asMap().keys.toSet()
两种方式都会生成当前缓存键的独立副本,后续缓存的过期操作不会影响这个快照,断言时就不会出现迭代异常。
2. 优化测试稳定性
Caffeine的过期清理是惰性触发的(通常在缓存被访问时执行),仅靠Thread.sleep(100)无法保证过期项已经被清理。可以在断言前主动触发一次缓存访问,确保过期项被清理:
@Test fun `should mark factor as used and wait for expiration`() { val factory = LocalFactorsRegistry(Duration.ofMillis(100)) factory.getUsedFactors() shouldBe emptySet() factory.markFactorAsUsed("factor2") Thread.sleep(150) // 适当延长等待时间,确保过期时间窗口已过 factory.getUsedFactors() // 触发缓存清理 factory.getUsedFactors() shouldBe emptySet() }
也可以直接调用Caffeine的cleanUp()方法(需要将factors的访问权限调整为internal,或者在测试中通过反射调用),不过上述方式更简洁且无需修改类的封装性。
内容的提问来源于stack exchange,提问作者pixel
相关产品推荐
相关产品推荐

