简体   繁体   English

使用 Moq 抛出异常模拟 IMemoryCache

[英]Mock IMemoryCache with Moq throwing exception

I'm trying to mock IMemoryCache with Moq.我正在尝试使用 Moq 模拟IMemoryCache I'm getting this error:我收到此错误:

An exception of type 'System.NotSupportedException' occurred in Moq.dll but was not handled in user code Moq.dll 中出现“System.NotSupportedException”类型的异常,但未在用户代码中处理

Additional information: Expression references a method that does not belong to the mocked object: x => x.Get<String>(It.IsAny<String>())附加信息:表达式引用了一个不属于模拟对象的方法:x => x.Get<String>(It.IsAny<String>())

My mocking code:我的嘲笑代码:

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

Why do I get that error?为什么我会收到那个错误?

This is the code under test:这是被测代码:

var cachedResponse = _memoryCache.Get<String>(url);

Where _memoryCache is of type IMemoryCache其中_memoryCache的类型为IMemoryCache

How do I mock the _memoryCache.Get<String>(url) above and let it return null?我如何模拟上面的_memoryCache.Get<String>(url)并让它返回 null?

Edit : How would I do the same thing but for _memoryCache.Set<String>(url, response);编辑:除了_memoryCache.Set<String>(url, response);我怎么做同样的事情_memoryCache.Set<String>(url, response); ? ? I don't mind what it returns, I just need to add the method to the mock so it doesn't throw when it is called.我不介意它返回什么,我只需要将该方法添加到模拟中,这样它在调用时就不会抛出。

Going by the answer for this question I tried:按照这个问题的答案,我试过:

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

Because in the memoryCache extensions it shows that it uses CreateEntry inside Set .因为在 memoryCache 扩展中它显示它在Set使用CreateEntry But it is erroring out with "object reference not set to an instance of an object".但它出错了“对象引用未设置为对象的实例”。

According to source code for MemoryCacheExtensions.cs ,根据MemoryCacheExtensions.cs 的源代码,

The Get<TItem> extension method makes use of the following 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;
}

Notice that essentially it is using the TryGetValue(Object, out Object) method.请注意,它本质上使用的是TryGetValue(Object, out Object)方法。

Given that it is not feasible to mock extension methods with Moq, Try mocking the interface members that are accessed by the extension methods.鉴于使用 Moq 模拟扩展方法是不可行的,请尝试模拟由扩展方法访问的接口成员。

Referring to Moq's quickstart update MockMemoryCacheService to properly setup the TryGetValue method for the test.参考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;
    }
}

From comments来自评论

Note that when mocking TryGetValue (in lieu of Get), the out parameter must be declared as an object even if it isn't.请注意,在模拟TryGetValue (代替 Get)时,即使不是object也必须将out参数声明为object

For example:例如:

 int expectedNumber = 1; object expectedValue = expectedNumber.

If you don't do this then it will match a templated extension method of the same name.如果您不这样做,那么它将匹配同名的模板化扩展方法。

Here is an example using the modified service of how to mock the memoryCache.Get<String>(url) and let it return null这是一个使用修改后的服务的示例,该示例如何模拟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);
}

UPDATE更新

The same process can be applied for the Set<> extension method which looks like this.相同的过程可以应用于如下所示的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;
}

This method makes use of the CreateEntry method which returns a ICacheEntry which is also acted upon.此方法使用CreateEntry方法,该方法返回一个ICacheEntry ,该方法也被执行。 So set up the mock to return a mocked entry as well like in the following example因此,设置模拟以返回模拟条目以及如下例所示

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

If you're calling the Set with a MemoryCacheEntryOptions and .AddExpirationToken , then you'll also need the entry to have a list of tokens.如果您使用MemoryCacheEntryOptions and .AddExpirationToken调用 Set,那么您还需要该条目以获取令牌列表。

This is an addition to @Nkosi's answer above.这是上面@Nkosi 回答的补充。 Example:例子:

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

The mock needs to include:模拟需要包括:

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

As pointed out by welrocken, there is no Get method in the interface you're trying to mock.正如 welrocken 所指出的,在您尝试模拟的接口中没有Get方法。 Nkosi has helpfully linked the source code for the extension methods which are the typical usages most people will make of the IMemoryCache . Nkosi 帮助链接了扩展方法的源代码,这是大多数人对IMemoryCache的典型用法。 Fundamentally, all of the extension methods call one of the three interface methods somewhere in their execution.从根本上说,所有扩展方法都在其执行过程中的某处调用了三个接口方法之一。

A quick and dirty way of inspecting what's going on is to setup a callback on all three of the mocked interface methods and stick a breakpoint in.检查正在发生的事情的一种快速而肮脏的方法是在所有三个模拟接口方法上设置回调并插入断点。

To specifically mock one of the Get methods, assuming that your test target method is calling Get , then you can mock that result like this:要专门模拟 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
        // ...
    }

EDIT: I've added an example on how to mock the setter in this answer .编辑:我在这个答案中添加了一个关于如何模拟 setter 的例子。

There is no Microsoft.Extensions.Caching.Memory.IMemoryCache.Get(object) method in the Microsoft.Extensions.Caching.Memory.IMemoryCache interface. Microsoft.Extensions.Caching.Memory.IMemoryCache接口中没有Microsoft.Extensions.Caching.Memory.IMemoryCache.Get(object)方法。 The one you try to use is in the Microsoft.Extensions.Caching.Memory.CacheExtensions .您尝试使用的是Microsoft.Extensions.Caching.Memory.CacheExtensions You can look at these answers to in-directly answer your question;您可以查看这些答案以间接回答您的问题;

How do I use Moq to mock an extension method? 如何使用 Moq 模拟扩展方法?

Mocking Extension Methods with Moq . 使用 Moq 模拟扩展方法

You should also be aware of how to configure Moq for later usages;您还应该了解如何配置 Moq 以供以后使用;

Your code states that the Get method returns a string, and takes a string parameter.您的代码指出 Get 方法返回一个字符串,并采用一个字符串参数。 If your test configuration follows that through out the whole test, it is fine.如果您的测试配置在整个测试过程中都遵循这一点,那就没问题了。 But by declaration, the Get method takes an object as a key.但是通过声明,Get 方法将一个对象作为键。 So your code for parameter predicate would be It.IsAny<object>() .所以你的参数谓词代码是It.IsAny<object>()

The second thing is, if you want to return null, you should cast that to the type your function actually returns (eg .Returns((string)null) ).第二件事是,如果你想返回 null,你应该将它转换为你的函数实际返回的类型(例如.Returns((string)null) )。 This is because there are other overloads for Moq.Language.IReturns.Returns , and the compiler cannot decide which one you are trying to refer to.这是因为Moq.Language.IReturns.Returns有其他重载,编译器无法决定您要引用哪一个。

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

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