模拟IMemoryCache与Moq抛出异常

Ben*_*ins 16 c# unit-testing moq .net-core asp.net-core

我正试图IMemoryCache用Moq 嘲笑.我收到这个错误:

Moq.dll中出现"System.NotSupportedException"类型的异常,但未在用户代码中处理

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

我的嘲弄代码:

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

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

这是测试中的代码:

var cachedResponse = _memoryCache.Get<String>(url);
Run Code Online (Sandbox Code Playgroud)

哪种_memoryCache类型IMemoryCache

我如何模拟_memoryCache.Get<String>(url)上面的内容并让它返回null?

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

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

mockMemoryCache
    .Setup(m => m.CreateEntry(It.IsAny<object>())).Returns(null as ICacheEntry);
Run Code Online (Sandbox Code Playgroud)

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

Nko*_*osi 32

根据MemoryCacheExtensions.cs的源代码,

Get<TItem>扩展的方法利用以下的

public static TItem Get<TItem>(this IMemoryCache cache, object key) {
    TItem value;
    cache.TryGetValue<TItem>(key, out value);
    return value;
}

public static bool TryGetValue<TItem>(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;
}
Run Code Online (Sandbox Code Playgroud)

请注意,基本上它正在使用该TryGetValue(Object, out Object)方法.

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

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

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

来自评论

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

例如:

int expectedNumber = 1; 
object expectedValue = expectedNumber. 
Run Code Online (Sandbox Code Playgroud)

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

这是一个使用如何模拟memoryCache.Get<String>(url)并让它返回的修改过的服务的例子null

[TestMethod]
public void _IMemoryCacheTestWithMoq() {
    var url = "fakeURL";
    object expected = null;

    var memoryCache = MockMemoryCacheService.GetMemoryCache(expected);

    var cachedResponse = memoryCache.Get<string>(url);

    Assert.IsNull(cachedResponse);
    Assert.AreEqual(expected, cachedResponse);
}
Run Code Online (Sandbox Code Playgroud)

UPDATE

对于Set<>看起来像这样的扩展方法,可以应用相同的过程.

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

    return value;
}
Run Code Online (Sandbox Code Playgroud)

该方法利用了CreateEntry返回a 的方法ICacheEntry.因此,设置模拟以返回模拟条目,如下例所示

[TestMethod]
public void _IMemoryCache_Set_With_Moq() {
    var url = "fakeURL";
    var response = "json string";

    var memoryCache = Mock.Of<IMemoryCache>();
    var cachEntry = Mock.Of<ICacheEntry>();

    var mockMemoryCache = Mock.Get(memoryCache);
    mockMemoryCache
        .Setup(m => m.CreateEntry(It.IsAny<object>()))
        .Returns(cachEntry);

    var cachedResponse = memoryCache.Set<string>(url, response);

    Assert.IsNotNull(cachedResponse);
    Assert.AreEqual(response, cachedResponse);
}
Run Code Online (Sandbox Code Playgroud)


use*_*074 12

Get正如 welrocken 所指出的,您尝试模拟的接口中没有方法。Nkosi 帮助链接了扩展方法的源代码,这些扩展方法是大多数人对IMemoryCache. 从根本上讲,所有扩展方法都会在执行过程中的某个地方调用三个接口方法之一。

检查正在发生的情况的一种快速而肮脏的方法是在所有三个模拟接口方法上设置回调并插入断点。

要专门模拟 Get 方法之一,假设您的测试目标方法正在调用Get,那么您可以像这样模拟该结果:

    delegate void OutDelegate<TIn, TOut>(TIn input, out TOut output);

    [Test]
    public void TestMethod()
    {
        // Arrange
        var _mockMemoryCache = new Mock<IMemoryCache>();
        object whatever;
        _mockMemoryCache
            .Setup(mc => mc.TryGetValue(It.IsAny<object>(), out whatever))
            .Callback(new OutDelegate<object, object>((object k, out object v) =>
                v = new object())) // mocked value here (and/or breakpoint)
            .Returns(true); 

        // Act
        var result = _target.GetValueFromCache("key");

        // Assert
        // ...
    }
Run Code Online (Sandbox Code Playgroud)

编辑:我在这个答案中添加了一个关于如何模拟设置器的示例。


Ali*_*ned 6

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

这是上面@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);
Run Code Online (Sandbox Code Playgroud)

模拟需要包括:

// https://github.com/aspnet/Caching/blob/45d42c26b75c2436f2e51f4af755c9ec58f62deb/src/Microsoft.Extensions.Caching.Memory/CacheEntry.cs
var cachEntry = Mock.Of<ICacheEntry>();
Mock.Get(cachEntry).SetupGet(c => c.ExpirationTokens).Returns(new List<IChangeToken>());

var mockMemoryCache = Mock.Get(memoryCache);
mockMemoryCache
    .Setup(m => m.CreateEntry(It.IsAny<object>()))
    .Returns(cachEntry);
Run Code Online (Sandbox Code Playgroud)