不指定所有输入参数类型的情况下获取方法引用

3
我想要使用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,所以它不知道T1是什么。
如何更改SetResult方法,以便仍然可以简单快速地引用Foo上的该方法,但不需要指定所有类型参数?
一些其他解除/约束条件:
- 您可以使用反射来收集有关该方法的信息(例如其输入参数)。 - 对SetResult的调用应尽可能简单,理想情况下只引用要设置的方法和返回值。 - SetResult必须可扩展到任意数量的输入类型(也就是说,上面示例调用中的bar也可能接受T2、T3等)。
- 我知道这需要多个SetResult定义。
- 理想情况下,我希望对过滤器使用可选参数,这些参数从null回退到It.IsAny(如代码示例中所示)。我可以放弃这些过滤器并对所有参数使用It.IsAny。
特定于我的代码示例的一些问题(忽略一般问题):
Moq允许模拟函数调用并检查输入参数。这些是一些常规设置,我的上述SetResult方法应该归结为其中最后一个使用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<Func<TObject,TResult>>(注意缺失的bar输入类型的定义),并检查它以模拟调用。任何 It.* 调用只能在此表达式中进行(请参见这个问题及其注释,了解我为什么使用 *.invoke())。

最终结果不仅将是设置结果的方法,还包括异常、顺序等...但这些对我来说并不重要。

1个回答

0
这可能需要一点改进,但它似乎接近你所说的内容。唯一的区别是它目前使用一个 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. 传递任何非默认过滤器,我们可能需要的

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接