【问题标题】:Get method-reference without specifying all input argument types在不指定所有输入参数类型的情况下获取方法引用
【发布时间】:2020-03-18 10:26:33
【问题描述】:

我想用 Moq 快速模拟一些方法调用。我确实有一些冗长的方法签名,它们的参数类型名称很长,而且,虽然我很懒,但当方法设置失败时,不想为所有这些都输入It.IsAny<>()无论如何输入参数的数量
更具体地说,我想出了这个扩展方法,但 T1(第一个参数)在示例调用中没有绑定:

public static void SetResult<T,T1,TResult>(this Mock<T> mock, Func<T, Func<T1, TResult>> method, TResult result, Func<T1> filterT1 = null) 
   where T : class
{
    if(filterT1 == null) 
    { 
       filterT1 = It.IsAny<T1>; 
    }
    mock.Setup(m => method.Invoke(m).Invoke(filterT1.Invoke()))
        .Returns(result);
}

// I want to use this like the following
Mock<Foo> mock = /* ... */;
mock.SetResult(foo => foo.bar, "Some return value");                   // Doesn't work
mock.SetResult<Foo, int, string>(foo => foo.bar, "Some return value"); // Works
mock.SetResult(foo => foo.bar, "Some return value", () => 42);         // Works too

现在我的 IDE 抱怨说它不知道 T1 是什么,因为在方法签名中没有明确使用该类型。

如何更改SetResult-Method,我仍然可以简单快速地引用Foo 上的方法,但无需指定所有类型参数?

更多缓解/约束:

  • 您可以使用反射来收集有关该方法的信息(例如其输入参数)。
  • SetResult 的调用应尽可能简单,最好只引用设置方法和返回值。
  • SetResult 必须可扩展为任意数量的输入类型(即,上述示例中的 bar 调用也可能采用 T2T3、...)
    • 我知道这需要多个 SetResult-Definitions。
  • 理想情况下,我想为过滤器使用可选参数,这些参数从null 回退到It.IsAny(如代码示例中所示)。我可以在没有这些过滤器的情况下生活,并为所有参数使用It.IsAny

一些特定于我的代码示例的绊线(忽略一般问题):

Moq 允许模拟函数调用并检查输入参数。这些是一些正常的设置,我上面的SetResult-Method 应该归结为(最后一个是IsAny)。

// "Normal" setups
mock.Setup(m => m.bar(42)).Returns("Some value")                     // Only allow input 42
mock.Setup(m => m.bar(It.Is(i => i % 2 == 0))).Returns("Some value") // Only allow even inputs
mock.Setup(m => m.bar(It.IsAny<int>())).Returns("Some value")        // Allow any int-input

Setup 需要一个Expression&lt;Func&lt;TObject,TResult&gt;&gt;(注意bar-input-types 缺少类型定义)并检查它以模拟调用。任何It.*-调用都只能在此表达式中进行(请参阅this question and its comments,了解我为什么使用*.invoke())。

最终结果不仅是设置结果的方法,还有异常、序列……
但我可以自己解决。

【问题讨论】:

    标签: c# generics moq


    【解决方案1】:

    这可能需要一些改进,但它似乎与您所谈论的内容接近。唯一的区别是它目前需要 MethodInfo 而不是 Func 来获取它应该模拟的方法。

    它遍历所有参数并使用System.Linq.Expression 为每个参数创建一个IsAny 表达式。

    public static class MoqExtension
    {
        public static void SetResult<T, TResult>(this Mock<T> mock, MethodInfo methodInfo, TResult result)
            where T : class
        {
            var expressions = new List<Expression>();
            // Create IsAny for each parameter
            foreach (var parameter in methodInfo.GetParameters())
            {
                var pType = parameter.ParameterType;
    
                var isAnyMethod = typeof(It).GetMethods((BindingFlags)(-1))
                    .Where(x => x.Name == "IsAny")
                    .First();
    
                var genericIsAnyMethod = isAnyMethod.MakeGenericMethod(pType);
    
                var isAnyExp = Expression.Call(genericIsAnyMethod);
                expressions.Add(isAnyExp);
            }
    
            // Create method call
            var argParam = Expression.Parameter(typeof(T), "x");
            var callExp = Expression.Call(argParam, methodInfo, expressions.ToArray());
            var lambda = Expression.Lambda<Func<T, TResult>>(callExp, argParam);
    
            // invoke setup method
            var mockType = mock.GetType();
            var setupMethod = mockType.GetMethods()
                .Where(x => x.Name == "Setup" && x.IsGenericMethod)
                .First();
            var genericMethod = setupMethod.MakeGenericMethod(typeof(TResult));
    
            var res = genericMethod.Invoke(mock, new object[] { lambda }) as ISetup<T, TResult>;
    
            res.Returns(result);
        }
    }
    

    用法示例:

        [Fact]
        public void MoqTestDynamic2()
        {
            var m = new Mock<ITestInterface>();
    
            m.SetResult(typeof(ITestInterface).GetMethod("GetAnotherInt"), 168);
    
            Assert.Equal(168, m.Object.GetAnotherInt("s", 1, 3));
            Assert.Equal(168, m.Object.GetAnotherInt("p", 1, 35));
            Assert.Equal(168, m.Object.GetAnotherInt(null, 1, 3));
        }
    
        public interface ITestInterface
        {
            int GetInt(string s);
    
            int GetAnotherInt(string s, int i, long l);
        }
    

    一定有更好的办法

    1. 表达我们要模拟的方法,而不是传递 MethodInfo
    2. 传入我们可能想要的任何非默认过滤器

    【讨论】:

      猜你喜欢
      • 2020-11-04
      • 2016-01-06
      • 1970-01-01
      • 2023-03-02
      • 2015-01-27
      • 1970-01-01
      • 2018-11-18
      • 1970-01-01
      • 2013-01-22
      相关资源
      最近更新 更多