模拟IMemoryCache与Moq抛出exception

我正试图用Moq模拟IMemoryCache 。 我收到这个错误:

Moq.dll中出现“System.NotSupportedException”类型的exception,但未在用户代码中处理

附加信息:表达式引用不属于模拟对象的方法:x => x.Get (It.IsAny ())

我的嘲弄代码:

 namespace Iag.Services.SupplierApiTests.Mocks { public static class MockMemoryCacheService { public static IMemoryCache GetMemoryCache() { Mock mockMemoryCache = new Mock(); mockMemoryCache.Setup(x => x.Get(It.IsAny())).Returns("");<---------- **ERROR** return mockMemoryCache.Object; } } } 

为什么我会收到这个错误?

这是测试中的代码:

 var cachedResponse = _memoryCache.Get(url); 

_memoryCache的类型为IMemoryCache

如何模拟上面的_memoryCache.Get(url)并让它返回null?

编辑 :我将如何做同样的事情,但对于_memoryCache.Set(url, response); ? 我不介意它返回什么,我只需要将方法添加到mock中,这样它就不会在调用时抛出。

我试着回答这个问题的答案:

 mockMemoryCache .Setup(m => m.CreateEntry(It.IsAny())).Returns(null as ICacheEntry); 

因为在memoryCache扩展中它显示它在Set使用CreateEntry 。 但它的错误是“对象引用没有设置为对象的实例”。

根据MemoryCacheExtensions.cs的源代码,

Get扩展方法使用以下内容

 public static TItem Get(this IMemoryCache cache, object key) { TItem value; cache.TryGetValue(key, out value); return value; } public static bool TryGetValue(this IMemoryCache cache, object key, out TItem value) { object result; if (cache.TryGetValue(key, out result)) { value = (TItem)result; return true; } value = default(TItem); return false; } 

请注意,它实际上是使用TryGetValue(Object, out Object)方法。

鉴于使用Moq模拟扩展方法是不可行的,请尝试模拟扩展方法访问的接口成员。

参考Moq的快速入门更新MockMemoryCacheService ,为测试正确设置TryGetValue方法。

 public static class MockMemoryCacheService { public static IMemoryCache GetMemoryCache(object expectedValue) { var mockMemoryCache = new Mock(); mockMemoryCache .Setup(x => x.TryGetValue(It.IsAny(), out expectedValue)) .Returns(true); return mockMemoryCache.Object; } } 

来自评论

请注意,在模拟TryGetValue (代替Get)时,必须将out参数声明为object即使它不是。

例如:

 int expectedNumber = 1; object expectedValue = expectedNumber. 

如果您不这样做,那么它将匹配同名的模板化扩展方法。

下面是一个使用如何模拟memoryCache.Get(url)并让它返回null的修改过的服务的示例

 [TestMethod] public void _IMemoryCacheTestWithMoq() { var url = "fakeURL"; object expected = null; var memoryCache = MockMemoryCacheService.GetMemoryCache(expected); var cachedResponse = memoryCache.Get(url); Assert.IsNull(cachedResponse); Assert.AreEqual(expected, cachedResponse); } 

UPDATE

可以对Set<>扩展方法应用相同的过程,如下所示。

 public static TItem Set(this IMemoryCache cache, object key, TItem value) { var entry = cache.CreateEntry(key); entry.Value = value; entry.Dispose(); return value; } 

此方法使用CreateEntry方法,该方法返回一个也被执行的ICacheEntry 。 因此,设置模拟以返回模拟条目,如下例所示

 [TestMethod] public void _IMemoryCache_Set_With_Moq() { var url = "fakeURL"; var response = "json string"; var memoryCache = Mock.Of(); var cachEntry = Mock.Of(); var mockMemoryCache = Mock.Get(memoryCache); mockMemoryCache .Setup(m => m.CreateEntry(It.IsAny())) .Returns(cachEntry); var cachedResponse = memoryCache.Set(url, response); Assert.IsNotNull(cachedResponse); Assert.AreEqual(response, cachedResponse); } 

如果您使用MemoryCacheEntryOptions and .AddExpirationToken调用Set,那么您还需要该条目以获得令牌列表。

这是@ Nkosi上面回答的补充。 例:

 // cache by filename: https://jalukadev.blogspot.com/2017/06/cache-dependency-in-aspnet-core.html var fileInfo = new FileInfo(filePath); var fileProvider = new PhysicalFileProvider(fileInfo.DirectoryName); var options = new MemoryCacheEntryOptions(); options.AddExpirationToken(fileProvider.Watch(fileInfo.Name)); this.memoryCache.Set(key, cacheValue, options); 

模拟需要包括:

 // https://github.com/aspnet/Caching/blob/45d42c26b75c2436f2e51f4af755c9ec58f62deb/src/Microsoft.Extensions.Caching.Memory/CacheEntry.cs var cachEntry = Mock.Of(); Mock.Get(cachEntry).SetupGet(c => c.ExpirationTokens).Returns(new List()); var mockMemoryCache = Mock.Get(memoryCache); mockMemoryCache .Setup(m => m.CreateEntry(It.IsAny())) .Returns(cachEntry); 

Microsoft.Extensions.Caching.Memory.IMemoryCache接口中没有Microsoft.Extensions.Caching.Memory.IMemoryCache.Get(object)方法。 您尝试使用的是Microsoft.Extensions.Caching.Memory.CacheExtensions 。 您可以查看这些答案,直接回答您的问题;

如何使用Moq模拟扩展方法?

使用Moq模拟扩展方法 。

您还应该了解如何为以后的用法配置Moq;

您的代码声明Get方法返回一个字符串,并采用字符串参数。 如果您的测试配置贯穿整个测试,那就没问题了。 但是通过声明,Get方法将对象作为键。 因此,参数谓词的代码将是It.IsAny()

第二件事是,如果你想返回null,你应该将它转换为你的函数实际返回的类型(例如.Returns((string)null) )。 这是因为Moq.Language.IReturns.Returns还有其他重载,编译器无法决定您尝试引用哪一个。