Unit testing of code that uses IMemoryCache

I am right now working with an ASP.NET Core 3.1 API where I wanted to use memory caching. The recommended way of that seems to be to use IMemoryCache. Because I am working test-driven I also want to write unit tests and then I, of course, want to test the parts of the code that are using IMemoryCache as well.

My first approach to that was to create a mock of the interface using Moq (our choice of mock framework in this project), but while for example if I use IMemoryCache wit the CacheExtensions (Microsoft.Extensions.Caching.Memory) that is not possible.

My second approach was to use the "real" implementation of it, Microsoft.Extensions.Caching.Memory.MemoryCache, because it was that implementation that was resolved when .NET Core resolved it. Make sure that you have added Microsoft.Extensions.Caching.Memory to your test project, I forgot that. Thanks to Erik Juhlin that you told me on Twitter.

var cacheOptions = Options.Create<MemoryCacheOptions>(new MemoryCacheOptions());
var cache = new MemoryCache(cacheOptions);

So my third approach and also a solution that works are to create your own implementation of IMemoryCache.

Before I created the IMemoryCache implementation I needed to create an implementation of ICacheEntry, which will represent an item in the cache. I decided that the AbsoluteExpirationRelativeToNow- and SlidingExpiration properties should update the AbsoluteExpiration. I am not sure if the "real" implementations is like that, but I got the same behavior in an easy way. Below can you see the complete mock-implementation of ICacheEntry:

public class CacheEntryMock : ICacheEntry
{
        private readonly object key;
 
        public CacheEntryMock(object key)
        {
            this.key = key;
        }
 
        public object Key => key;
 
        public object Value { get; set; }
        public DateTimeOffset? AbsoluteExpiration { get; set; }
 
        private TimeSpan? absoluteExpirationRelativeToNow;
        public TimeSpan? AbsoluteExpirationRelativeToNow
        {
            get => absoluteExpirationRelativeToNow;
            set
            {
                absoluteExpirationRelativeToNow = value;
 
                if (value.HasValue)
                {
                    AbsoluteExpiration = DateTimeOffset.Now.Add(value.Value); 
                }
                else
                {
                    AbsoluteExpiration = null;
                }
            }
}

When I had implemented ICacheEntry I continued with the IMemoryCache interface. I just used a simple Dictionary to store the entries in.
In the TryGetValues method I check if the time for AbsoluteExpiration has passed if so I return true and sets value parameter to null. If the SlidingExpiration property is set I also update the value for AbsoluteExpiration with a new "period" based on the value of the SlidingExpiration property. Here is the complete mock-implementation of IMemoryCache:

public class MemoryCacheMock : IMemoryCache
{
        private IDictionary<object, ICacheEntry> cache = new Dictionary<object, ICacheEntry>();
 
        public ICacheEntry CreateEntry(object key)
        {
            var entry = new CacheEntryMock(key);
 
            if(cache.ContainsKey(key))
            {
                cache[key] = entry;
            }
            else
            {
                cache.Add(key, entry);
            }
 
            return entry;
        }
 
        public void Dispose()
        {
 
        }
 
        public void Remove(object key)
        {
            cache.Remove(key);
        }
 
        public bool TryGetValue(object key, out object value)
        {
            if(!cache.ContainsKey(key))
            {
                value = null;
                return false;
            }
 
            var entry = cache[key];
 
            if(entry.AbsoluteExpiration.HasValue && entry.AbsoluteExpiration > DateTimeOffset.Now)
            {
                value = null;
                return false;
            }
 
            if(entry.SlidingExpiration.HasValue)
            {
                entry.AbsoluteExpiration = DateTimeOffset.Now.Add(entry.SlidingExpiration.Value);
            }
 
            value = entry.Value;
 
            return true;
        }
}

With this code written I was ready to start to write unit tests for my code!

One thought on “Unit testing of code that uses IMemoryCache

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.