【问题标题】:Moq It.IsAnyType not working for Func returning Task with generic typeMoq It.IsAnyType 不适用于具有泛型类型的 Func 返回任务
【发布时间】:2020-07-18 06:47:56
【问题描述】:

我尝试了 3 种不同的方法来为通用接口方法设置模拟。只有一种方法有效,但它使用的是显式类型,因此不能通用。我尝试使用 It.IsAnyType,但它似乎与所进行的调用不匹配。

这是示例代码(我希望测试用例 1 会返回“asdf”)。如何让模拟适用于任何类型(不仅仅是字符串?)?

using Moq;
using System;
using System.Threading.Tasks;

namespace blah
{
    public class Junk : IGetOrAddable
    {
        public static void Main()
        {
            Test(1);
            Test(2);
            Test(3);
            /*********OUTPUT*********
            For test case 1, value is null
            For test case 2, value is asdf
            Unhandled exception. System.ArgumentException: Object of type 
                'System.Func`2[System.Object,System.Threading.Tasks.Task`1[System.String]]'
                cannot be converted to type 
                'System.Func`2[System.Object,System.Threading.Tasks.Task`1[Moq.It+IsAnyType]]'.
            *************************/
        }

        public static void Test(int testCase)
        {
            Mock<IGetOrAddable> mock = new Mock<IGetOrAddable>();

            //setup the mock to always call the valueFactory function (ignore cache)
            switch(testCase)
            {
                case 1:
                {
                    //use the It.IsAnyType to match any generic invocation of this method
                    mock.Setup(x => x.GetOrAdd<It.IsAnyType>(It.IsAny<object>(), It.IsAny<Func<object, Task<It.IsAnyType>>>()))
                        .Returns((object k, Func<object, Task<It.IsAnyType>> f) => f(k));
                    break;
                }
                case 2:
                {
                    //use an exact type (string?) to match a specific type invocation of this method
                    mock.Setup(x => x.GetOrAdd<string?>(It.IsAny<object>(), It.IsAny<Func<object, Task<string?>>>()))
                        .Returns((object k, Func<object, Task<string?>> f) => f(k));
                    break;
                }
                case 3:
                {
                    //try casting It.IsAny<object> per this suggestion: https://stackoverflow.com/a/61322568/352349
                    mock.Setup(x => x.GetOrAdd<It.IsAnyType>(It.IsAny<object>(), (Func<object, Task<It.IsAnyType>>)It.IsAny<object>()))
                        .Returns((object k, Func<object, Task<It.IsAnyType>> f) => f(k));
                    break;
                }
            }
            
            var value = mock.Object.GetOrAdd<string?>(new object(), RetrieveCoolValue).Result;
            Console.WriteLine($"For test case {testCase}, value is {value ?? "null"}");
        }
        public Task<T> GetOrAdd<T>(object key, Func<object, Task<T>> valueFactory)
        {
            //complicated cache retrieval stuff here of the sort described in https://docs.microsoft.com/en-us/dotnet/api/system.collections.concurrent.concurrentdictionary-2.getoradd?view=netcore-3.1
            throw new NotImplementedException();
        }
        public static Task<string?> RetrieveCoolValue(object key)
        {
            return Task.FromResult<string?>("asdf");
        }
    }

    public interface IGetOrAddable
    {
        Task<T> GetOrAdd<T>(object key, Func<object, Task<T>> valueFactory);
    }

}

【问题讨论】:

  • 请注意,如果您在第一种情况下尝试使用 mock 调用 Verify(),您将收到 此设置不匹配 错误。看来,编译器无法使用 It.IsAnyType 推断泛型类型的使用
  • @PavelAnikhouski 是的,不匹配是这个谜团的核心。
  • 现有的question 也存在几乎相同的问题。不幸的是,没有答案。我想,您可以在Moq github 中创建问题
  • 好的,我创建了一个起订量问题 - github.com/moq/moq4/issues/1040
  • 对于测试用例 1,调用与设置不匹配(根据问题 61317501)。对于测试用例 3,调用确实与设置匹配,但您不能使用匹配器作为返回类型 (Task&lt;It.IsAnyType&gt;)。我使用DefaultValueProvider 尝试自动设置这样的通用模拟,这不是很好,但我不知道有什么替代方法。如果您想要一个实际的例子,请参阅github.com/rgvlee/LazyCache.Testing/blob/master/src/…

标签: c# moq func generic-method


【解决方案1】:

好吧,直到 github 问题得到解决(或出现更好的答案),我遵循 rgvlee 的建议并使用 rgvlee 的一些代码通过 DefaultValueProvider 设置模拟。最终代码如下所示:

using Moq;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;

namespace blah
{
    /// <summary>
    /// A default value provider for Mocks, lets you specify the value to return in a func.
    /// </summary>
    public class SomeGenericDefaultValueProvider : DefaultValueProvider
    {
        private readonly Func<Type, Mock, MethodInfo, IReadOnlyList<object>, object> _f;
        public SomeGenericDefaultValueProvider(Func<Type, Mock, MethodInfo, IReadOnlyList<object>, object> f)
        {
            _f = f;
        }

        protected override object GetDefaultValue(Type type, Mock mock)
        {
            var lastInvocation = mock.Invocations.Last();
            var methodInfo = lastInvocation.Method;
            var args = lastInvocation.Arguments;

            try
            {
                return _f(type, mock, methodInfo, args);
            }
            catch(NotImplementedException)
            {
                //just return default for that type
                if (type.IsValueType)
                {
                    return Activator.CreateInstance(type);
                }
                return null;
            }
        }
    }

    public class Junk : IGetOrAddable
    {
        public static void Main()
        {
            Mock<IGetOrAddable> mock = new Mock<IGetOrAddable>();

            //setup the mock to always call the valueFactory function (ignore cache)
            mock.DefaultValueProvider = new SomeGenericDefaultValueProvider((type, mock, methodInfo, args) =>
            {
                if (methodInfo.Name == "GetOrAdd")
                {
                    object key = args[0];
                    dynamic valueFactory = args[1];
                    return valueFactory(key);
                }
                throw new NotImplementedException(); //else throw this so we can let regular behavior occur
            });
                        
            var value = mock.Object.GetOrAdd<string?>(new object(), RetrieveCoolValue).Result;
            Console.WriteLine($"For test, value is {value ?? "null"}");
        }
        public Task<T> GetOrAdd<T>(object key, Func<object, Task<T>> valueFactory)
        {
            //complicated cache retrieval stuff here of the sort described in https://docs.microsoft.com/en-us/dotnet/api/system.collections.concurrent.concurrentdictionary-2.getoradd?view=netcore-3.1
            throw new NotImplementedException();
        }
        public static Task<string?> RetrieveCoolValue(object key)
        {
            return Task.FromResult<string?>("asdf");
        }
    }

    public interface IGetOrAddable
    {
        Task<T> GetOrAdd<T>(object key, Func<object, Task<T>> valueFactory);
    }
}

【讨论】:

    猜你喜欢
    • 2020-06-13
    • 1970-01-01
    • 2020-07-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-07-02
    相关资源
    最近更新 更多