Search code examples
mockingxunitlazycache

Lazy Cache mocking GetorAddasync()


My service code is below:

var cachedLatestVersion = await _cache.GetOrAddAsync("latestVersion", latestVersion, DateTimeOffset.Now.AddHours(24));

I have tried to mock the IAppache interface and set up a value for the GetOrAddAsync method but an error occurred

_cacheMock.Setup(c => c.GetOrAddAsync(It.IsAny<string>(), It.IsAny<Func<Task<string>>>(), It.IsAny<DateTimeOffset>())).ReturnsAsync("latestVersion");

The error:

System.NotSupportedException : Unsupported expression: c => c.GetOrAddAsync(It.IsAny(), It.IsAny<Func<Task>>(), It.IsAny()) Extension methods (here: AppCacheExtensions.GetOrAddAsync) may not be used in setup / verification expressions.

Then I have come up with another solution which is creating a new instance of MockCachingService():

var mockedCache = new MockCachingService(); var cacheMock = Mock.Get(mockedCache);

But I got another type of error

System.ArgumentException : Object instance was not created by Moq. (Parameter 'mocked')


Solution

  • You can't mock extensions, you'll need to mock the interface member that is invoked in the extension. You'll need to inspect it but guessing it'll be one of the following:

    Task<T> GetOrAddAsync<T>(string key, Func<ICacheEntry, Task<T>> addItemFactory);
    Task<T> GetOrAddAsync<T>(string key, Func<ICacheEntry, Task<T>> addItemFactory, MemoryCacheEntryOptions policy);
    

    Source: https://github.com/alastairtree/LazyCache/blob/master/LazyCache/IAppCache.cs

    Probably the second one:

    _cacheMock.Setup(c => c.GetOrAddAsync(It.IsAny<string>(), It.IsAny<Func<ICacheEntry, Task<string>>>(), It.IsAny<MemoryCacheEntryOptions>())).ReturnsAsync("latestVersion");
    

    The mock caching service included in the library is not a Moq mock, it's closer to a fake implementation. That's why Mock.Get won't work on it.

    Alternatively, the unit testing wiki entry covers alternative usages that may suit e.g., use the real thing. For tests using the latter, I use the following to clear the cache after every test:

    [TearDown]
    public void TearDown()
    {
       var cacheProvider = CachingService.CacheProvider;
       var memoryCache = (MemoryCache) cacheProvider.GetType().GetField("cache", BindingFlags.Instance | BindingFlags.NonPublic).GetValue(cacheProvider);
       memoryCache.Compact(1.0);
    }
    

    If you wish to continue mocking the caching service, the library LazyCache.Testing (disclaimer: I am the author) offers easy ready to go implementations for Moq and NSubstitute that'll allow you to assert invocations.