简体   繁体   English

XUnit如何模拟IMemoryCache ASP.NET Core

[英]XUnit how to mock IMemoryCache ASP.NET Core

I understand IMemoryCache.Set is an extension method so it can not be mocked. 我了解IMemoryCache.Set是一种扩展方法,因此无法模拟。 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. 我想知道如何为我的数据访问层实现这一目标,其中我的MemoryCache返回一个值,而当找不到时,它从db获取数据,将其设置为MemoryCache并返回所需的值。

    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 以下是我的单元测试-当然,它不能正常工作,因为我无法模拟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 该代码位于此处: 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. 因此,从中可以看到它访问CreateEnty并期望有一个对象。 Then it calls SetOptions and assigns Value on the entry. 然后,它将调用SetOptions并在条目上分配Value

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. 您可以修改实现并使它做您想做的任何事情。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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