Moq It.IsAnyType not working for Func returning Task with generic type

微笑、不失礼 提交于 2021-02-10 07:53:51

问题


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.

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

}


回答1:


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


来源:https://stackoverflow.com/questions/62940053/moq-it-isanytype-not-working-for-func-returning-task-with-generic-type

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!