@@ -66,19 +66,13 @@ public async Task DefaultEvictionBehavior_RemovesAbsoluteExpiredItem_LeavesNonEx
6666 const string expiredKey = "expired" ;
6767 const string notExpiredKey = "notExpired" ;
6868
69- var cache = new Dictionary < string , CacheEntity < string , IAsyncDisposable > >
70- {
71- {
72- notExpiredKey , new CacheEntity < string , IAsyncDisposable > ( notExpiredKey , ( ) => Task . FromResult ( notExpiredCacheObject ) , AsyncLazyFlags . None )
73- . WithAbsoluteExpiration ( DateTimeOffset . UtcNow . AddDays ( 2 ) )
74- } ,
75- {
76- expiredKey , new CacheEntity < string , IAsyncDisposable > ( expiredKey , ( ) => Task . FromResult ( expiredCacheObject ) , AsyncLazyFlags . None )
77- . WithAbsoluteExpiration ( DateTimeOffset . UtcNow . AddMinutes ( - 1 ) )
78- }
79- } ;
69+ var expiredItem = new CacheEntity < string , IAsyncDisposable > ( expiredKey , ( ) => Task . FromResult ( expiredCacheObject ) , AsyncLazyFlags . None )
70+ . WithAbsoluteExpiration ( DateTimeOffset . UtcNow . AddMinutes ( - 1 ) ) ;
8071
81- await Internal_DefaultEvictionBehavior_RemovesExpiredItem_LeavesNonExpiredItems ( cache , expiredKey , notExpiredKey , expiredCacheObject , notExpiredCacheObject ) ;
72+ var notExpiredItem = new CacheEntity < string , IAsyncDisposable > ( notExpiredKey , ( ) => Task . FromResult ( notExpiredCacheObject ) , AsyncLazyFlags . None )
73+ . WithAbsoluteExpiration ( DateTimeOffset . UtcNow . AddDays ( 2 ) ) ;
74+
75+ await Internal_DefaultEvictionBehavior_RemovesExpiredItem_LeavesNonExpiredItems ( expiredItem , notExpiredItem , expiredCacheObject , notExpiredCacheObject ) ;
8276 }
8377
8478 [ Fact ]
@@ -90,63 +84,48 @@ public async Task DefaultEvictionBehavior_RemovesSlidingExpiredItem_LeavesNonExp
9084 const string expiredKey = "expired" ;
9185 const string notExpiredKey = "notExpired" ;
9286
93- var cache = new Dictionary < string , CacheEntity < string , IAsyncDisposable > >
94- {
95- {
96- notExpiredKey , new CacheEntity < string , IAsyncDisposable > ( notExpiredKey , ( ) => Task . FromResult ( notExpiredCacheObject ) , AsyncLazyFlags . None )
97- . WithSlidingExpiration ( TimeSpan . FromDays ( 2 ) )
98- } ,
99- {
100- expiredKey , new CacheEntity < string , IAsyncDisposable > ( expiredKey , ( ) => Task . FromResult ( expiredCacheObject ) , AsyncLazyFlags . None )
101- . WithSlidingExpiration ( TimeSpan . FromTicks ( 1 ) )
102- }
103- } ;
87+ var expiredItem = new CacheEntity < string , IAsyncDisposable > ( expiredKey , ( ) => Task . FromResult ( expiredCacheObject ) , AsyncLazyFlags . None )
88+ . WithSlidingExpiration ( TimeSpan . FromTicks ( 1 ) ) ;
89+
90+ var notExpiredItem = new CacheEntity < string , IAsyncDisposable > ( notExpiredKey , ( ) => Task . FromResult ( notExpiredCacheObject ) , AsyncLazyFlags . None )
91+ . WithSlidingExpiration ( TimeSpan . FromDays ( 2 ) ) ;
10492
105- await Internal_DefaultEvictionBehavior_RemovesExpiredItem_LeavesNonExpiredItems ( cache , expiredKey , notExpiredKey , expiredCacheObject , notExpiredCacheObject ) ;
93+ await Internal_DefaultEvictionBehavior_RemovesExpiredItem_LeavesNonExpiredItems ( expiredItem , notExpiredItem , expiredCacheObject , notExpiredCacheObject ) ;
10694 }
10795
10896 private static async Task Internal_DefaultEvictionBehavior_RemovesExpiredItem_LeavesNonExpiredItems (
109- Dictionary < string , CacheEntity < string , IAsyncDisposable > > cache ,
110- string expiredKey ,
111- string notExpiredKey ,
97+ CacheEntity < string , IAsyncDisposable > expiredItem ,
98+ CacheEntity < string , IAsyncDisposable > notExpiredItem ,
11299 IAsyncDisposable expiredCacheObject ,
113100 IAsyncDisposable notExpiredCacheObject )
114101 {
115102 var timeProvider = new FakeTimeProvider ( DateTime . UtcNow ) ;
116-
117103 var target = new DefaultEvictionBehavior ( timeProvider ) ;
118104
119- var evt = new ManualResetEvent ( false ) ;
105+ var resetEvent = new ManualResetEvent ( false ) ;
106+ var cache = Substitute . For < IDictionary < string , CacheEntity < string , IAsyncDisposable > > > ( ) ;
107+ _ = cache . Configure ( )
108+ . Values
109+ . Returns ( [ expiredItem , notExpiredItem ] )
110+ . AndDoes ( _ => resetEvent . Set ( ) ) ;
111+
112+ cache . Remove ( Arg . Any < string > ( ) ) . Returns ( true ) ;
120113
121- var expiredCacheItems = new Dictionary < string , IAsyncDisposable > ( ) ;
122114 var config = new AsyncMemoryCacheConfiguration < string , IAsyncDisposable >
123115 {
124- CacheItemExpired = async ( s , item ) =>
125- {
126- expiredCacheItems [ s ] = item ;
127-
128- // Bit of a hack
129- // We need to wait for it to complete one tick, and DisposeAsync() waits for the worker task to complete
130- // And as such guarantees at least one tick completion
131- await target . DisposeAsync ( ) ;
132- _ = evt . Set ( ) ;
133- } ,
134116 CacheBackingStore = cache
135117 } ;
136118
137119 target . Start ( config , _logger ) ;
138120
139121 timeProvider . Advance ( TimeSpan . FromSeconds ( 30 ) ) ;
140122
141- _ = evt . WaitOne ( ) ;
142-
143- Assert . True ( cache . ContainsKey ( notExpiredKey ) ) ;
144- Assert . False ( cache . ContainsKey ( expiredKey ) ) ;
145-
146- Assert . True ( expiredCacheItems . ContainsKey ( expiredKey ) ) ;
147- Assert . False ( expiredCacheItems . ContainsKey ( notExpiredKey ) ) ;
123+ // Will ensure that the internal loop has completed at least one tick
124+ _ = resetEvent . WaitOne ( ) ;
125+ await target . DisposeAsync ( ) ;
148126
149- Assert . Same ( expiredCacheObject , expiredCacheItems [ expiredKey ] ) ;
127+ cache . Received ( 1 ) . Remove ( expiredItem . Key ) ;
128+ cache . DidNotReceive ( ) . Remove ( notExpiredItem . Key ) ;
150129
151130 await expiredCacheObject . Received ( 1 ) . DisposeAsync ( ) ;
152131 await notExpiredCacheObject . DidNotReceive ( ) . DisposeAsync ( ) ;
@@ -277,4 +256,52 @@ public async Task CacheItemWithoutExpirationStrategy_NeverDisposed()
277256
278257 await expiredCacheObject . DidNotReceive ( ) . DisposeAsync ( ) ;
279258 }
259+
260+ [ Fact ]
261+ public async Task DefaultEvictionBehavior_CallsCallbacks_ForExpiredItem ( )
262+ {
263+ const string expiredKey = "expired" ;
264+
265+ var resetEvent = new ManualResetEvent ( false ) ;
266+ var globalCacheItemExpiredCallbackCalled = false ;
267+ var globalCacheItemExpiredCallback = ( string key , IAsyncDisposable value ) => { globalCacheItemExpiredCallbackCalled = true ; } ;
268+ var cacheItemExpirationCallbackCalled = false ;
269+ var cacheItemExpirationCallback = ( string key , IAsyncDisposable value ) =>
270+ {
271+ cacheItemExpirationCallbackCalled = true ;
272+ resetEvent . Set ( ) ;
273+ } ;
274+
275+ var expiredCacheObject = Substitute . For < IAsyncDisposable > ( ) ;
276+ var cache = Substitute . For < IDictionary < string , CacheEntity < string , IAsyncDisposable > > > ( ) ;
277+ _ = cache . Configure ( )
278+ . Values
279+ . Returns (
280+ [
281+ new CacheEntity < string , IAsyncDisposable > ( expiredKey , ( ) => Task . FromResult ( expiredCacheObject ) , AsyncLazyFlags . None )
282+ . WithAbsoluteExpiration ( DateTimeOffset . UtcNow )
283+ . WithExpirationCallback ( cacheItemExpirationCallback )
284+ ] )
285+ . AndDoes ( _ => resetEvent . Set ( ) ) ;
286+
287+ cache . Remove ( expiredKey ) . Returns ( true ) ;
288+
289+ var timeProvider = new FakeTimeProvider ( DateTime . UtcNow ) ;
290+ var target = new DefaultEvictionBehavior ( timeProvider ) ;
291+ var config = new AsyncMemoryCacheConfiguration < string , IAsyncDisposable >
292+ {
293+ CacheItemExpired = globalCacheItemExpiredCallback ,
294+ CacheBackingStore = cache
295+ } ;
296+
297+ target . Start ( config , _logger ) ;
298+
299+ timeProvider . Advance ( TimeSpan . FromSeconds ( 30 ) ) ;
300+ resetEvent . WaitOne ( ) ;
301+
302+ await target . DisposeAsync ( ) ;
303+
304+ Assert . True ( cacheItemExpirationCallbackCalled ) ;
305+ Assert . True ( globalCacheItemExpiredCallbackCalled ) ;
306+ }
280307}
0 commit comments