Подтвердить что ты не робот

В чем разница между передачей It.IsAny <int>() и значением It.IsAny <int>() в настройке метода

Я использую Moq и хочу создавать классы-конструкторы для создания мотов с предустановленными разумными значениями по умолчанию, которые могут быть переопределены при настройке теста по мере необходимости. Подход, который я использовал, использует методы расширения, в которых я передаю значения входных параметров и ожидаемый результат. При этом я вижу различное поведение в том, что кажется мне семантически эквивалентным кодом: передача It.IsAny() непосредственно в настройке и передача значения It.IsAny() косвенно в настройке. Пример:

public interface IFoo
{
    bool Bar(int value);
    bool Bar2(int value);
}
public class Foo : IFoo
{
    public bool Bar(int value) { return false; }
    public bool Bar2(int value) { return false; }
}

var mock = new Mock<IFoo>();
mock.Setup(x => x.Bar(It.IsAny<int>())).Returns(true);
Assert.IsTrue(mock.Object.Bar(123));                    // Succeeds

var myValue = It.IsAny<int>();
mock.Setup(x => x.Bar2(myValue)).Returns(true);
Assert.IsTrue(mock.Object.Bar2(123));                   // Fails

Оба вызова эквивалентны (мне), но вызов Bar2 не дает подтверждения. Почему это?

4b9b3361

Ответ 1

It.IsAny позволяет только Moq сопоставлять будущие вызовы вызовов методов, если они используются в конструкции Setup. Когда Setup называется Moq, он просто добавляет вызов метода в свой кэш уже настроенных вызовов метода. Обратите внимание, что аргумент Setup в вашем примере имеет тип Expression<Func<IFoo, bool>>. Поскольку вы проходите в Expression, вызов фактического метода не вызывается, а Moq имеет возможность пересекать выражение, чтобы выяснить, какие параметры вызова метода были явными и которые являются аргументами It.IsAny. Он использует эту способность для определения того, соответствует ли вызов будущего метода во время выполнения одним из вызовов уже настроенных методов.

Чтобы сделать так, чтобы метод Bar мог принимать аргумент It.IsAny<int>(), необходимо сделать It.IsAny<int>() return a int (так как это тип параметра Bar). В общем случае возвращаемый тип It.IsAny<T> должен быть T. Необходимо выбрать произвольное значение T. Наиболее естественным выбором является default(T), который работает для ссылочных типов и типов значений. (Подробнее о ключевом слове по умолчанию здесь). В вашем случае это default(int), который равен 0.

Итак, когда вы действительно оцениваете It.IsAny<int>(), сразу возвращается значение 0. Однако, когда вы используете It.IsAny<int>() в Expression (как в аргументе метода Setup), тогда древовидная структура вызова метода сохраняется, а Moq может сопоставлять будущие вызовы метода на вызов метода, инкапсулированный Expression.

Итак, хотя вы не можете сохранить It.IsAny<int>() как переменную каким-либо значимым образом, вы можете сохранить весь Expression в переменной:

Expression<Func<IFoo, bool>> myExpr = x => x.Bar2(It.IsAny<int>());
mock.Setup(myExpr).Returns(true);
Assert.IsTrue(mock.Object.Bar2(123));  

Наконец, я просто хочу напомнить вам, что Moq является открытым исходным кодом. Источник доступен здесь. Мне очень полезно иметь этот исходный код, чтобы я мог щелкнуть и изучить код и модульные тесты.

Ответ 2

It.IsAny<int>() имеет тип возврата int и возвращает 0, поэтому ваша вторая настройка эквивалентна:

mock.Setup(x => x.Bar2(0)).Returns(true);

Я не проверял код moq, но я уверен, что когда он оценивает выражение в методе установки, он учитывает, что параметр на самом деле является It.IsAny против обычного числа.

Вам лучше, если вы создадите настройки непосредственно в своих вспомогательных методах и не пропустите It.IsAny.