简体   繁体   English

不使用 Mock.Setup() 调用 Mock.Returns()

[英]Call Mock.Returns() without Mock.Setup()

When using Moq, you often need to configure your mock to return specific values.使用 Moq 时,您通常需要配置模拟以返回特定值。 To specify what you want to return, you have to go through the routine of defining Setup() with following structure before you get to define what your method should return:要指定要返回的内容,在定义方法应返回的内容之前,您必须通过定义具有以下结构的 Setup() 的例程:

var o = new ObjectToReturn();

myMock.Setup(m => m.MyMethod(It.IsAny<T1>()...It.IsAny<Tn>()))
      .Returns(o);

Instead, I would like to do "For methods with this name, return this value", something like:相反,我想做“对于具有此名称的方法,返回此值”,例如:

myMock.Setup(m => m.GetType().GetMethod("MyMethod")).Returns(o);

or或者

myMock.Setup("MyMethod").Return(o);

Is there any other way to skip this verbose enumeration of all method parameters when I don't care about types, values or number of parameters at all?当我根本不关心类型、值或参数数量时,还有其他方法可以跳过所有方法参数的详细枚举吗?

I know there is a method SetReturnsDefault() but I don't want to setup default values for all the methods of the mock.我知道有一个方法SetReturnsDefault()但我不想为模拟的所有方法设置默认值。

This is possible.这个有可能。 Try:尝试:

public interface IService
{
    int MyMethod1(int a, object b);

    int MyMethod2(int a);
    int MyMethod2(object b);
}

public static class MyMockExtensions
{
    public static ISetup<T, TResult> Setup<T, TResult>(this Mock<T> mock, 
        string methodName) where T : class
    {
        var methods = typeof(T).GetMethods()
            .Where(
                mi => mi.Name == methodName
                && mi.ReturnType == typeof(TResult))
            .ToArray();

        if (methods.Length == 0)
        {
            throw new MissingMethodException("No method found.");
        }

        if (methods.Length > 1)
        {
            throw new AmbiguousMatchException("Ambiguous methods found.");
        }

        var method = methods[0];

        // Figure out parameters.
        var parameters = method.GetParameters()
            // It.IsAny<pi.ParameterType>()
            .Select(pi => Expression.Call(
                typeof(It), nameof(It.IsAny), new[] { pi.ParameterType }));

        // arg0 => arg0.MyMethod(It.IsAny<T1>()...It.IsAny<Tn>())
        var arg0 = Expression.Parameter(typeof(T), "arg0");
        var setupExpression = Expression.Lambda<Func<T, TResult>>(
            Expression.Call(arg0, method, parameters), arg0);

        return mock.Setup(setupExpression);
    }
}

Usage:用法:

var mock = new Mock<IService>();

mock.Setup<IService, int>("MyMethod1").Returns(123);
// method1Result = 123
var method1Result = mock.Object.MyMethod1(1, 2);

// This throws AmbiguousMatchException exception as there are two MyMethod3
// both returning an integer.
mock.Setup<IService, int>("MyMethod2").Returns(234);

The challenge would be dealing with different types of methods defined by your mocked type:挑战将是处理由您的模拟类型定义的不同类型的方法:

  • Normal method普通法
  • Generic method通用方法
  • Method overloading方法重载

The default value provider is potentially an option.默认值提供程序可能是一个选项。 This isn't the same as SetReturnsDefault , you've got a lot more control.这与SetReturnsDefault ,您有更多的控制权。

A real quick MVP default value provider such as the following:一个真正快速的 MVP默认值提供者,例如:

public class SelectiveDefaultValueProvider : DefaultValueProvider
{
    private readonly string _methodName;
    private readonly object _returns;

    public SelectiveDefaultValueProvider(string methodName, object returns)
    {
        _methodName = methodName;
        _returns = returns;
    }

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

        if (methodInfo.Name.Equals(_methodName))
        {
            return _returns;
        }

        return type.IsValueType ? Activator.CreateInstance(type) : null;
    }
}

...allows you to inject some decision making before the value is returned. ...允许您在返回值之前注入一些决策。 In this case I'm checking the last invocation method name, if it's a match I'm returning the nominated object to return.在这种情况下,我正在检查最后一个调用方法名称,如果匹配,我将返回指定的对象以返回。 I'm not using the args variable but I've included it to show that you've not only got the MethodInfo of the last invocation, but also the provided arguments.我没有使用 args 变量,但我已经包含它以表明您不仅获得了上次调用的MethodInfo ,而且还获得了提供的参数。 Enough to make clever decisions.足以做出明智的决定。

Take the following interface with a few overloaded methods and the same return type:采用以下接口,其中包含一些重载方法和相同的返回类型:

public class ObjectToReturn
{
    public Guid Id { get; set; }
}

public interface IFoo
{
    ObjectToReturn MyMethod(int parameter1);

    ObjectToReturn MyMethod(string parameter2);

    ObjectToReturn MyMethod(int parameter1, int parameter2);

    ObjectToReturn AnotherMethod();

    int AValueTypeMethod();
}

The test setup would look like测试设置看起来像

[Test]
public void DefaultValueProvider_ForOverloadedMethod_AllOverloadsReturnSameExpectedResult()
{
    var objectToReturn = new ObjectToReturn { Id = Guid.NewGuid() };
    var mock = new Mock<IFoo> { DefaultValueProvider = new SelectiveDefaultValueProvider(nameof(IFoo.MyMethod), objectToReturn) };
    var mocked = mock.Object;

    var result1 = mocked.MyMethod(1);
    var result2 = mocked.MyMethod(1, 2);
    var result3 = mocked.MyMethod("asdf");
    var result4 = mocked.AnotherMethod();
    var result5 = mocked.AValueTypeMethod();

    Assert.Multiple(() =>
    {
        Assert.That(result1, Is.SameAs(objectToReturn));
        Assert.That(result2, Is.SameAs(objectToReturn));
        Assert.That(result3, Is.SameAs(objectToReturn));
        Assert.That(result4, Is.Null);
        Assert.That(result5, Is.TypeOf<int>());
    });
}

As mentioned the above is MVP, you could easily extend the default value provider implementation to take a list of methods/return values, tighten up the interface from string method names to MethodInfo results ( typeof(IFoo).GetMethods().Where(x => x.Name.StartsWith("MyMethod") && x.ReturnType == typeof(ObjectToReturn)) ) and so on.如上所述是 MVP,您可以轻松扩展默认值提供程序实现以获取方法/返回值列表,将接口从字符串方法名称收紧到MethodInfo结果( typeof(IFoo).GetMethods().Where(x => x.Name.StartsWith("MyMethod") && x.ReturnType == typeof(ObjectToReturn)) ) 等等。

The nice thing about this methodology is you don't have to worry about building Setup expressions and the headaches that that brings, and if you do specify a setup for a member it'll use that setup;这种方法的好处是您不必担心构建Setup表达式和由此带来的麻烦,如果您确实为成员指定了设置,它将使用该设置; the default value provider is only used for members that haven't had a setup specified.默认值提供程序仅用于尚未指定设置的成员。

Working solution 工作解决方案

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM