Moq: Как получить параметр, передаваемый методу смоделированной службы

169

Представьте себе этот класс

public class Foo {

    private Handler _h;

    public Foo(Handler h)
    {
        _h = h;
    }

    public void Bar(int i)
    {
        _h.AsyncHandle(CalcOn(i));
    }

    private SomeResponse CalcOn(int i)
    {
        ...;
    }
}

Mo (q) cking Handler в тесте Foo, как я смогу проверить, что Bar()было передано _h.AsyncHandle?

январь
источник
Вы имели в виду "AsyncHandle" (дополнительная "n")? И не могли бы вы опубликовать код для Handler, или указать полное имя типа, если это стандартный тип?
TrueWill
Можете ли вы показать свой скелетный тест, чтобы показать, что вы думаете? Хотя я ценю, что с вашей стороны это очевидно, с нашей стороны это выглядит так, как будто кто-то не нашел времени, чтобы сделать вопрос ответственным, не сделав длинного умозрительного ответа.
Рубен Бартелинк
1
Здесь нет ни Foo, ни Bar (), ни чего-либо подобного. Это просто некоторый демонстрационный код, чтобы показать ситуацию, в которой я нахожусь, не отвлекаясь от специфики приложения. И я получил только ответ, я надеялся получить.
Янв

Ответы:

283

Вы можете использовать метод Mock.Callback:

var mock = new Mock<Handler>();
SomeResponse result = null;
mock.Setup(h => h.AnsyncHandle(It.IsAny<SomeResponse>()))
    .Callback<SomeResponse>(r => result = r);

// do your test
new Foo(mock.Object).Bar(22);
Assert.NotNull(result);

Если вы хотите проверить только что-то простое в переданном аргументе, вы также можете сделать это напрямую:

mock.Setup(h => h.AnsyncHandle(It.Is<SomeResponse>(response => response != null)));
Gamlor
источник
36
Примечание: если у вас есть несколько аргументов для вашей функции, вам нужно указать все типы в общем Callback<>()методе Moq. Например, если у вашего метода есть определение Handler.AnsyncHandle(string, SomeResponse), вам нужно /* ... */.Callback<string, SomeResponse>(r => result = r);. Я не нашел этого явно заявленного во многих местах, поэтому я решил добавить это сюда.
Фрэнк Брайс
12
Просто хотел исправить @Frank, если вы не видели ответ @JavaJudt. Правильный способ получить два аргумента:/* ... */.Callback<string, SomeResponse>((s1, s2) => { str1 = s1; result = s2});
renatogbp
2
Вы также можете добиться того же, просто объявив типы аргументов в лямбда-выражении, например так:.Callback((string s1, SomeResponse s2) => /* stuff */ )
jb637
1
Не могли бы вы обновить свой ответ, чтобы включить ссылку на встроенный Capture.Inпомощник?
Као
29

Ответ Гэмлора сработал для меня, но я подумал, что расширю комментарий Джона Карпентера, потому что я искал решение, включающее более одного параметра. Я полагал, что другие люди, которые натыкаются на эту страницу, могут быть в подобной ситуации. Я нашел эту информацию в документации Moq .

Я буду использовать пример Gamlor, но давайте представим, что метод AsyncHandle принимает два аргумента: a stringи SomeResponseобъект.

var mock = new Mock<Handler>();
string stringResult = string.Empty;
SomeResponse someResponse = null;
mock.Setup(h => h.AsyncHandle(It.IsAny<string>(), It.IsAny<SomeResponse>()))
    .Callback<string, SomeResponse>((s, r) => 
    {
        stringResult = s;
        someResponse = r;
    });

// do your test
new Foo(mock.Object).Bar(22);
Assert.AreEqual("expected string", stringResult);
Assert.IsNotNull(someResponse);

По сути, вам просто нужно добавить еще один It.IsAny<>()с соответствующим типом, добавить еще один тип в Callbackметод и изменить лямбда-выражение в зависимости от ситуации.

JavaJudt
источник
22

Метод Callback, безусловно, будет работать, но если вы делаете это для метода с большим количеством параметров, он может быть немного многословным. Вот кое-что, что я использовал, чтобы удалить часть шаблона.

var mock = new Mock<Handler>();

// do your test   
new Foo(mock.Object).Bar(22);

var arg = new ArgumentCaptor<SomeResponse>();
mock.Verify(h => h.AsyncHandle(arg.Capture()));
Assert.NotNull(arg.Value);

Вот источник для ArgumentCaptor:

public class ArgumentCaptor<T>
{
    public T Capture()
    {
        return It.Is<T>(t => SaveValue(t));
    }

    private bool SaveValue(T t)
    {
        Value = t;
        return true;
    }

    public T Value { get; private set; }
}
Эндрю Рэдфорд
источник
21

Ответ Гамлора работает, но другой способ сделать это (и тот, который я считаю более выразительным в тесте) - это ...

var mock = new Mock<Handler>();
var desiredParam = 47; // this is what you want to be passed to AsyncHandle
new Foo(mock.Object).Bar(22);
mock.Verify(h => h.AsyncHandle(desiredParam), Times.Once());

Проверка очень мощная, и стоит потратить время, чтобы привыкнуть.

Пит Мартин
источник
15
Этот подход хорош, если вы просто хотите проверить, вызван ли метод с известным параметром. В случае, когда параметр еще не создан в момент написания теста (например, рассматриваемая единица создает параметр внутри), тогда Callback позволяет вам захватить и запросить это, тогда как ваш подход не будет.
Майкл
1
Мне нужно сохранить переданное значение, потому что мне нужно убедиться, что весь набор объектов был передан.
MrFox,
Кроме того, иногда параметр является сущностью, и вы хотите отдельные утверждения для каждого поля в сущности. Лучший способ сделать это - захватить параметр, а не использовать Verify и matcher.
Кевин Вонг
12

Альтернативой также является использование Capture.Inфункции moq. Это moqфункция OOTB, которая позволяет захватывать аргументы в коллекции.

//Arrange
var args = new List<SomeResponse>();
mock.Setup(h => h.AnsyncHandle(Capture.In(args)));

//Act
new Foo(mock.Object).Bar(22);

//Assert
//... assert args.Single() or args.First()
Джонни
источник
1
Отличный ответ! Я не знал о классах Moq.Capture и Moq.CaptureMatch, пока не увидел этот ответ. Захват - лучшая альтернатива CallbackИМО. Поскольку вы используете Capture непосредственно в списке параметров, он гораздо менее подвержен проблемам при рефакторинге списка параметров метода и, следовательно, делает тесты менее хрупкими. С Callback вы должны поддерживать параметры, передаваемые в программе установки, в синхронизации с параметрами типа, используемыми для Callback, и это определенно вызывало у меня проблемы в прошлом.
Джастин Хольцер
7

Вы могли бы использовать It.Is<TValue>()matcher.

var mock = new Mock<Handler>();
new Foo(mock.Object).Bar(22);
mock.Verify(h => h.AsyncHandle(It.Is<SomeResponse>(r => r != null )));
Эд Яблонский
источник
2

Это также работает:

Mock<InterfaceThing> mockedObject = new Mock<InterfaceThing>();
var objectParameter = mockedObject.Invocations[1].Arguments[0] as ObjectParameter;
Джефф Смит
источник
2

Здесь много хороших ответов! Идите с готовым набором функций Moq, пока вам не понадобится сделать утверждения о нескольких параметрах класса, передаваемых вашим зависимостям. Однако, если вы окажетесь в такой ситуации, функция Moq Verify с It.Is matchers не поможет изолировать неудачу теста, а способ захвата аргументов Returns / Callback добавляет ненужные строки кода в ваш тест (и долгие тесты мне не нужны).

Вот суть: https://gist.github.com/Jacob-McKay/8b8d41ebb9565f5fca23654fd944ac6b с расширением Moq (4.12), которое я написал и которое дает более декларативный способ делать утверждения об аргументах, передаваемых имитам, без вышеупомянутых недостатков. Вот как выглядит раздел Verify:

        mockDependency
            .CheckMethodWasCalledOnce(nameof(IExampleDependency.PersistThings))
            .WithArg<InThing2>(inThing2 =>
            {
                Assert.Equal("Input Data with Important additional data", inThing2.Prop1);
                Assert.Equal("I need a trim", inThing2.Prop2);
            })
            .AndArg<InThing3>(inThing3 =>
            {
                Assert.Equal("Important Default Value", inThing3.Prop1);
                Assert.Equal("I NEED TO BE UPPER CASED", inThing3.Prop2);
            });

Я был бы рад, если бы Moq предоставил функцию, которая выполняет то же самое, будучи в то же время декларативной и обеспечивая изоляцию ошибок, что делает это. Скрещенные пальцы!

Джейкоб Маккей
источник
1
Мне это нравится. Verify of Moq конкурирует с Assert of xUnit за право выполнения утверждений. Это не правильно на части Moq установки. Настройка функции It.Is также должна поддерживать не выражения.
Томас
«Moq конкурирует с Assert of xUnit за авторитет выполнения утверждений», - хорошо сказал @Thomas. Я добавил бы, что это проиграло бы соревнование. Moq умеет сообщать, был ли соответствующий вызов, но конкретные подтверждения дают вам гораздо лучшую информацию. Основным недостатком моего подхода является потеря безопасности типов и проверка порядка параметров. Некоторое время я искал улучшения по этому поводу, надеюсь, есть C # ниндзя, который может взломать пример вместе! В противном случае, если я найду способ, я обновлю это.
Джейкоб Маккей