简体   繁体   中英

XUnit how to mock IMemoryCache ASP.NET Core

I understand IMemoryCache.Set is an extension method so it can not be mocked. People have provided workarounds to such situation eg as one by the NKosi here . I am wondering how I can achieve that for my data access layer where my MemoryCache returns a value and when not found it gets data from the db, set it to the MemoryCache and return the required value.

    public string GetMessage(int code)
    {
        if(myMemoryCache.Get("Key") != null)
        {
            var messages= myMemoryCache.Get<IEnumerable<MyModel>>("Key");
            return messages.Where(x => x.Code == code).FirstOrDefault().Message;
        }

        using (var connection = dbFactory.CreateConnection())
        {
            var cacheOptions = new MemoryCacheEntryOptions { SlidingExpiration = TimeSpan.FromHours(1) };
            const string sql = @"SELECT Code, Message FROM MyTable";

            var keyPairValueData = connection.Query<KeyPairValueData>(sql);

            myMemoryCache.Set("Key", keyPairValueData, cacheOptions );

            return keyPairValueData.Where(x => x.Code == code).FirstOrDefault().Message;
        }
    }

Following is my Unit Test - And off course it is not working as I can't mock IMemoryCache

    [Fact]
    public void GetMessage_ReturnsString()
    {
        //Arrange
        // Inserting some data here to the InMemoryDB

        var memoryCacheMock = new Mock<IMemoryCache>();

        //Act
        var result = new DataService(dbConnectionFactoryMock.Object, memoryCacheMock.Object).GetMessage(1000);

        //assert xunit
        Assert.Equal("Some message", result);
    }

The first thing I would say is why not use a real memory cache? It would verify the behavior much better and there's no need to mock it:

// Arrange
var memCache = new MemoryCache("name", new NameValueCollection());

//Act
var result = new DataService(dbConnectionFactoryMock.Object, memCache).GetMessage(1000);

// Assert: has been added to cache
memCache.TryGetValue("Key", out var result2);
Assert.Equal("Some message", result2);

// Assert: value is returned
Assert.Equal("Some message", result);

If you really want to mock it out, here's a guide on how to do that:

Because it's an extension method, you need to make sure that it can be called as is. What happens in your case is that the extension method will call into the mock. Since you provide no expected behavior, it will probably fail.

You need to look at the code for the extension method, check what it accesses and then ensure that your mock complies with the expected behavior. The code is available here: https://github.com/aspnet/Caching/blob/master/src/Microsoft.Extensions.Caching.Abstractions/MemoryCacheExtensions.cs#L77

This is the code:

public static TItem Set<TItem>(this IMemoryCache cache, object key, TItem value, MemoryCacheEntryOptions options)
    {
        using (var entry = cache.CreateEntry(key))
        {
            if (options != null)
            {
                entry.SetOptions(options);
            }

            entry.Value = value;
        }

        return value;
    }

So, from that, you can see that it accesses CreateEnty and expects an object from it. Then it calls SetOptions and assigns Value on the entry.

You could mock it like this:

var entryMock = new Mock<ICacheEntry>();
memoryCacheMock.Setup(m => m.CreateEntry(It.IsAny<object>())
               .Returns(entryMock.Object);

// maybe not needed
entryMock.Setup(e => e.SetOptions(It.IsAny<MemoryCacheEntryOptions>())
         ...

When you do this, the extension method will be called on the mock and it will return the mocked entry. You can modify the implementation and make it do whatever you want.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM