Вызовите Mock.Returns () без Mock.Setup () - PullRequest
0 голосов
/ 25 марта 2020

При использовании Moq вам часто необходимо настроить макет так, чтобы он возвращал указанные c значения. Чтобы указать, что вы хотите вернуть, вы должны go через процедуру определения Setup () со следующей структурой, прежде чем определить, что должен возвращать ваш метод:

var o = new ObjectToReturn();

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

Вместо этого я бы хотел сделать "Для методов с этим именем, вернуть это значение", что-то вроде:

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

или

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

Есть ли другой способ пропустить это подробное перечисление всех параметров метода когда мне наплевать на типы, значения или количество параметров вообще?

Я знаю, что есть метод SetReturnsDefault () , но я не хочу устанавливать значения по умолчанию для всех методов макета.

Ответы [ 2 ]

1 голос
/ 25 марта 2020

Поставщик значения по умолчанию потенциально является опцией. Это не то же самое, что SetReturnsDefault, у вас намного больше контроля.

A очень быстрый 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;
    }
}

... позволяет вам вводить некоторые решения до того, как значение будет возвращено. В этом случае я проверяю имя последнего метода вызова, если это совпадение, я возвращаю номинированный объект для возврата. Я не использую переменную args, но включил ее, чтобы показать, что вы получили не только MethodInfo последнего вызова, но и предоставленные аргументы. Достаточно принять умные решения.

Возьмите следующий интерфейс с несколькими перегруженными методами и с тем же типом возврата:

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

Настройка теста будет выглядеть как

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

Как уже упоминалось выше, это MVP, вы можете легко расширить реализацию поставщика значений по умолчанию, чтобы получить список методов / возвращаемых значений, сжать интерфейс от имен строковых методов до MethodInfo результатов (typeof(IFoo).GetMethods().Where(x => x.Name.StartsWith("MyMethod") && x.ReturnType == typeof(ObjectToReturn))) и т. Д.

Приятной особенностью этой методологии является то, что вам не нужно беспокоиться о построении Setup выражений и головной боли, которую это приносит, и если вы укажете настройку для члена, она будет использовать эту настройку; поставщик значений по умолчанию используется только для элементов, для которых не задана настройка.

Рабочий раствор

1 голос
/ 25 марта 2020

Это возможно. Попробуйте:

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

Использование:

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

Задача будет касаться различных типов методов, определенных вашим типом:

  • Обычный метод
  • Generi c метод
  • Перегрузка метода
...