Можете ли вы помочь мне понять обратный вызов Moq?

100

Используя Moq и посмотрел, Callbackно я не смог найти простой пример, чтобы понять, как его использовать.

У вас есть небольшой рабочий фрагмент, который четко объясняет, как и когда его использовать?

user9969
источник

Ответы:

85

Трудно превзойти https://github.com/Moq/moq4/wiki/Quickstart

Если это недостаточно ясно, я бы назвал это ошибкой документа ...

РЕДАКТИРОВАТЬ: В ответ на ваше разъяснение ...

Для каждого Setupвыполняемого имитационного метода вы можете указать такие вещи, как:

  • ограничения на входы
  • значение для / способ, которым должно быть получено возвращаемое значение (если оно есть)

.CallbackМеханизм говорит : «Я не могу описать это прямо сейчас, но когда вызов в форме , как это происходит, перезвони мне , и я буду делать то , что должно быть сделано». В рамках той же самой цепочки вызовов вы можете управлять результатом для возврата (если есть) через .Returns". В примерах QS примером является то, что они увеличивают возвращаемое значение каждый раз.

В общем, вам не понадобится такой механизм очень часто (в тестовых шаблонах xUnit есть термины для антипаттернов, подобных условной логике в тестах), и если есть более простой или встроенный способ установить, что вам нужно, он должен быть используется в предпочтении.

Об этом рассказывается в части 3 из 4 серии Moq Джастина Этериджа , и здесь есть еще один пример обратных вызовов.

Простой пример обратного вызова можно найти в статье Использование обратных вызовов с публикацией Moq .

Рубен Бартелинк
источник
4
Привет, Рубен, я изучаю Moq, и если тебе нравится, я набираю много примеров, чтобы понять, как делать что-то с его помощью. Моя проблема в том, что я не понимаю, когда его использовать. Как только я пойму, что проблема решена, я напишу свой собственный код. Если бы вы объяснили это своим собственным словом, когда бы вы использовали обратный вызов? спасибо цените ваше время
user9969
17
Трудно превзойти [ссылка]? Не за что. Эта ссылка показывает вам, как делать десятки разных вещей, но не говорит вам, зачем вам это нужно делать. Я обнаружил, что это обычная проблема в издевательской документации. Я могу с нуля пересчитать количество хороших, ясных объяснений насмешек TDD +, которые я нашел. Большинство предполагает уровень знаний, который, будь он у меня, мне бы не понадобился для чтения статьи.
Райан Ланди,
@Kyralessa: Я понимаю вашу точку зрения. Лично у меня появилось довольно много книжных знаний, так что я нашел материал для быстрого старта абсолютно идеальным. К сожалению, я не знаю лучшего примера, чем те, на которые я ссылался в конце сообщения. Если вы найдете такой, опубликуйте его здесь, и я буду счастлив отредактировать его (или
сделайте это самостоятельно
«Я сделаю то, что нужно сделать, и скажу вам результат, который нужно вернуть (если есть)» Я думаю, что это вводит в заблуждение, AFAIU не Callbackимеет ничего общего с возвращаемым значением (если вы случайно не связали его с помощью кода). По сути, он только гарантирует, что обратный вызов вызывается до или после каждого вызова (в зависимости от того, связали ли вы его до или после Returnsсоответственно), просто и просто.
Охад Шнайдер
1
@OhadSchneider По моей ссылке ... Вы правы! Интересно (но не очень интересно, так как не использовал Moq в течение длительного времени), если интерфейс Fluent изменился (не кажется вероятным, т.е. я сделал ошибочное предположение и не прочитал то, что я связал, поскольку я обычно работал с этим из автозаполнения сам). Надеюсь, исправление
затронет
60

Вот пример использования обратного вызова для тестирования объекта, отправленного в службу данных, которая обрабатывает вставку.

var mock = new Mock<IDataService>();
DataEntity insertedEntity = null;

mock.Setup(x => x.Insert(It.IsAny<DataEntity>())).Returns(1) 
           .Callback((DataEntity de) => insertedEntity = de);

Альтернативный синтаксис универсального метода:

mock.Setup(x => x.Insert(It.IsAny<DataEntity>())).Returns(1) 
           .Callback<DataEntity>(de => insertedEntity = de);

Тогда вы можете протестировать что-то вроде

Assert.AreEqual("test", insertedEntity.Description, "Wrong Description");
Джефф Холл
источник
4
Возможно, для этого конкретного случая (в зависимости от того, пытаетесь ли вы выразить тесты для состояния или поведения), в некоторых случаях может быть проще использовать It.Is<T>in a Mock.Verifyвместо того, чтобы засорять тест темпами. Но +1, потому что я уверен, что есть много людей, которым лучше всего подойдет пример.
Рубен Бартелинк, 03
10

В CallbackMoq есть два типа . Один происходит до того, как вызов возвращается; другое происходит после возврата вызова.

var message = "";
mock.Setup(foo => foo.Execute(arg1: "ping", arg2: "pong"))
    .Callback((x, y) =>
    {
        message = "Rally on!";
        Console.WriteLine($"args before returns {x} {y}");
    })
    .Returns(message) // Rally on!
    .Callback((x, y) =>
    {
        message = "Rally over!";
        Console.WriteLine("arg after returns {x} {y}");
    });

В обоих обратных вызовах мы можем:

  1. проверить аргументы метода
  2. аргументы метода захвата
  3. изменить контекстное состояние
Шон Латтин
источник
2
Фактически, оба происходят до возврата вызова (что касается вызывающего). См. Stackoverflow.com/a/28727099/67824 .
Охад Шнайдер
8

Callbackэто просто средство для выполнения любого пользовательского кода, который вы хотите, при вызове одного из методов имитации. Вот простой пример:

public interface IFoo
{
    int Bar(bool b);
}

var mock = new Mock<IFoo>();

mock.Setup(mc => mc.Bar(It.IsAny<bool>()))
    .Callback<bool>(b => Console.WriteLine("Bar called with: " + b))
    .Returns(42);

var ret = mock.Object.Bar(true);
Console.WriteLine("Result: " + ret);

// output:
// Bar called with: True
// Result: 42

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

var cq = new ConcurrentQueue<bool>();
mock.Setup(f => f.Bar(It.IsAny<bool>())).Callback<bool>(cq.Enqueue);
Parallel.Invoke(() => mock.Object.Bar(true), () => mock.Object.Bar(false));
Console.WriteLine("Invocations: " + String.Join(", ", cq));

// output:
// Invocations: True, False

Кстати, пусть вас не смущает вводящее в заблуждение различие «до Returns» и «после Returns». Это просто техническое различие в том, будет ли ваш пользовательский код выполняться после Returnsоценки или раньше. В глазах вызывающего абонента оба будут выполняться до того, как будет возвращено значение. Действительно, если метод voidвозвращается, вы даже не можете его вызвать, Returnsно он работает так же. Для получения дополнительной информации см. Https://stackoverflow.com/a/28727099/67824 .

Охад Шнайдер
источник
1

Помимо других хороших ответов здесь, я использовал его для выполнения логики, прежде чем генерировать исключение. Например, мне нужно было сохранить все объекты, которые были переданы методу для последующей проверки, и этот метод (в некоторых тестовых случаях) должен был вызвать исключение. Вызов не .Throws(...)на Mock.Setup(...)переопределение в Callback()действие и не вызывает ее. Однако, выбрасывая исключение в обратном вызове, вы по-прежнему можете выполнять все полезные действия, которые может предложить обратный вызов, и по-прежнему генерировать исключение.

Фрэнк Брайс
источник