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) переглядаючи Хендлера в тесті на Foo, як я міг би перевірити, що Bar()пройшло _h.AsyncHandle?


Ви мали на увазі "AsyncHandle" (додатково "n")? І чи можете ви опублікувати код для Handler або вказати повністю кваліфіковане ім’я типу, якщо це стандартний тип?
TrueWill

Чи можете ви показати тест на скелет, щоб показати, що ви думаєте? Хоча я ціную, що з вашого боку це очевидно, з нашої сторони, схоже, хтось, хто не витратив час, щоб зробити питання відповідальним, не роблячи довгого спекулятивного відповіді.
Рубен Бартелінк,

1
Немає ні Foo, ні Bar (), ні подібного. Це лише деякий демо-код, який показує ситуацію, в якій я перебуваю, не відволікаючись від специфіки застосування. І я отримав просто відповідь, яку я сподівався отримати.
Jan

Відповіді:


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

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помічник?
cao

29

Відповідь Гамлора працювала на мене, але я думав, що розширюся на коментар Джона Карпентера, тому що я шукав рішення, що включає більш ніж один параметр. Я зрозумів, що інші люди, які натрапляють на цю сторінку, можуть опинитися в подібній ситуації. Я знайшов цю інформацію в документації Moq .

Я буду використовувати приклад Гамлора, але давайте зробимо вигляд, що метод 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методу та змінити вираз лямбда, якщо потрібно.


22

Метод зворотного виклику, безумовно, спрацює, але якщо ви робите це на методі з великою кількістю параметрів, він може бути трохи дослідним. Ось щось, що я використав, щоб зняти частину котла.

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
Цей підхід чудово, якщо ви просто хочете перевірити, чи викликається метод із відомим параметром. У випадку, коли параметр ще не створений в момент написання тесту (наприклад, розглянутий блок створює параметр внутрішньо), тоді зворотний виклик дозволяє вам захопити та допитати це, тоді як ваш підхід не буде.
Майкл

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 безпосередньо у списку параметрів, він набагато менш схильний до проблем при рефакторингу списку параметрів методу, а тому робить тести менш крихкими. За допомогою зворотного виклику вам потрібно тримати параметри, передані в програмі Setup, синхронізовано з параметрами типу, використовуваними для зворотного виклику, і це, безумовно, викликало у мене проблеми в минулому.
Джастін Хольцер

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 не справляється з хорошою ізоляцією тестової помилки, а спосіб повернення / зворотного виклику збору аргументів додає до вашого тесту непотрібні рядки коду (і довгі випробування для мене не підуть).

Ось суть: 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 xUnit за повноваження виконувати твердження. Це не відповідає правильній частині Moq у налаштуваннях. Налаштування функції It.Is також має підтримувати не вирази.
Томас

"Moq конкурує з Assert xUnit за повноваження виконувати твердження" - Добре сказав @Thomas. Я додам, що це втратить конкуренцію. Moq добре повідомляє вам, чи був відповідний дзвінок, але конкретні твердження дають вам набагато кращу інформацію. Основним недоліком мого підходу є втрата безпеки типу і перевірка параметрів. Я деякий час шукав вдосконалення щодо цього, сподіваюся, там є ніндзя C #, який може зламати приклад разом! Інакше, якщо знайду спосіб, я це оновлю.
Джейкоб Маккей
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.