Moq It.IsAnyType不适用于具有泛型类型的Func返回Task [英] Moq It.IsAnyType not working for Func returning Task with generic type

查看:53
本文介绍了Moq It.IsAnyType不适用于具有泛型类型的Func返回Task的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我尝试了3种不同的方法来为通用接口方法设置模拟.只有一种方法可以使用,但是它使用的是显式类型,因此不会通用.我尝试使用It.IsAnyType,但在拨打的电话上似乎不匹配.

I've tried 3 different ways to setup a mock for a generic interface method. Only one way works, but it is using an explicit type, so won't work generically. I tried using It.IsAnyType, but it doesn't seem to match on the call that is made.

这是示例代码(我希望测试用例1返回"asdf").我怎样才能使模拟适用于任何类型(不仅仅是字符串?)?

Here is the sample code (I expected test case 1 to have "asdf" returned). How can I get the mock to work for any type (not just string?)?

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

}

推荐答案

好,直到解决了github问题(或者出现了更好的答案),我听从了rgvlee的建议,并使用了rgvlee的一些代码通过DefaultValueProvider.最终代码如下所示:

Well, until that github issue is fixed (or a better answer comes along), I followed rgvlee's advice and used some of rgvlee's code to setup the mock via a DefaultValueProvider. Here's what the final code looks like:

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

这篇关于Moq It.IsAnyType不适用于具有泛型类型的Func返回Task的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆