代码之家  ›  专栏  ›  技术社区  ›  Learning Curve

XUnit如何模拟IMemoryCache ASP.NET内核

  •  1
  • Learning Curve  · 技术社区  · 7 年前

    我理解IMemoryCache.Set是一个扩展方法,所以它不能被模仿。人们已经为这种情况提供了解决办法,例如NKosi提供的解决办法 here . 我想知道,对于我的数据访问层,我的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;
            }
        }
    

    下面是我的单元测试-当然它不起作用,因为我不能模仿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);
        }
    
    1 回复  |  直到 7 年前
        1
  •  6
  •   Kenneth    7 年前

    我要说的第一件事是为什么不使用真正的内存缓存?它可以更好地验证行为,而且无需嘲笑:

    // 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);
    

    您需要查看扩展方法的代码,检查它访问什么,然后确保mock符合预期的行为。代码可从以下位置获得: https://github.com/aspnet/Caching/blob/master/src/Microsoft.Extensions.Caching.Abstractions/MemoryCacheExtensions.cs#L77

    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;
        }
    

    所以,从这里,你可以看到它访问 CreateEnty 并期望从中得到一个对象。然后它会打电话来 SetOptions Value 在入口。

    你可以这样嘲笑它:

    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>())
             ...
    

    执行此操作时,将对mock调用扩展方法,并返回mock条目。您可以修改实现并让它做任何您想做的事情。