Können Sie mir helfen, Moq Callback zu verstehen?

95

Verwenden von Moq und angeschaut, Callbackaber ich konnte kein einfaches Beispiel finden, um zu verstehen, wie man es verwendet.

Haben Sie einen kleinen Arbeitsausschnitt, der klar erklärt, wie und wann Sie ihn verwenden sollen?

user9969
quelle

Antworten:

82

Schwer zu schlagen https://github.com/Moq/moq4/wiki/Quickstart

Wenn das nicht klar genug ist, würde ich das einen Doc-Fehler nennen ...

EDIT: Als Antwort auf Ihre Klarstellung ...

Für jede verspottete Methode, die SetupSie ausführen, können Sie Folgendes angeben:

  • Einschränkungen für Eingaben
  • Der Wert für / Weise, in dem der Rückgabewert (falls vorhanden) abgeleitet werden soll

Der .CallbackMechanismus sagt: "Ich kann es momentan nicht beschreiben, aber wenn ein Anruf in dieser Form auftritt, rufen Sie mich zurück und ich werde tun, was getan werden muss." Als Teil derselben fließenden Aufrufkette können Sie das Ergebnis steuern, das (falls vorhanden) über .Returns"zurückgegeben werden soll. In den QS-Beispielen wird beispielsweise der zurückgegebene Wert jedes Mal erhöht.

Im Allgemeinen benötigen Sie einen solchen Mechanismus nicht sehr oft (xUnit-Testmuster enthalten Begriffe für Antimuster der ilk Conditional Logic In Tests), und wenn es eine einfachere oder integrierte Methode gibt, um festzustellen, was Sie benötigen, sollte dies der Fall sein bevorzugt verwendet.

Teil 3 von 4 in Justin Etheredges Moq-Serie behandelt es, und hier gibt es ein weiteres Beispiel für Rückrufe

Ein einfaches Beispiel für einen Rückruf finden Sie unter Verwenden von Rückrufen mit Moq- Post.

Ruben Bartelink
quelle
3
Hallo Ruben, ich lerne Moq und wenn du magst, buldige ich viele Beispiele, um zu verstehen, wie man Dinge damit macht. Mein Problem ist, dass ich nicht verstehe, wann ich es verwenden soll. Sobald ich das Problem gelöst habe, schreibe ich meinen eigenen Code. Wenn Sie es in Ihrem eigenen Wort erklären würden, wann würden Sie Rückruf verwenden? danke schätze deine Zeit
user9969
15
Schwer zu schlagen [Link]? Überhaupt nicht. Dieser Link zeigt Ihnen, wie Sie Dutzende verschiedener Dinge tun, sagt Ihnen aber nicht, warum Sie eines davon tun müssen. Was ein häufiges Problem beim Verspotten von Dokumentation ist, habe ich gefunden. Ich kann auf null Finger zählen, wie viele gute, klare Erklärungen für TDD + Spott ich gefunden habe. Die meisten gehen von einem Wissensstand aus, den ich, wenn ich ihn hätte, nicht lesen müsste.
Ryan Lundy
@ Kyralessa: Ich verstehe deinen Standpunkt. Ich persönlich hatte einiges an Buchkenntnissen und fand das Schnellstart-Zeug absolut perfekt. Leider ist mir kein besseres Beispiel bekannt als das, auf das ich am Ende des Beitrags verlinkt habe. Sollten Sie eine finden, posten Sie sie hier und ich werde sie gerne bearbeiten (oder gerne bei DIY)
Ruben Bartelink
"Ich werde tun, was getan werden muss, und Ihnen das Ergebnis mitteilen, das zurückgegeben werden soll (falls vorhanden)." Ich denke, dies ist irreführend. AFAIU Callbackhat nichts mit dem Rückgabewert zu tun (es sei denn, Sie verknüpfen es zufällig über Code). Grundsätzlich wird nur sichergestellt, dass der Rückruf vor oder nach jedem Aufruf aufgerufen wird (je nachdem, ob Sie ihn vor oder nach dem Aufruf verkettet Returnshaben).
Ohad Schneider
1
@OhadSchneider Folgen Sie meinem Link ... Sie sind richtig! Ich frage mich (aber nicht wirklich interessiert genug, da ich Moq schon lange nicht mehr verwendet habe), ob sich die Fluent-Oberfläche geändert hat (scheint nicht wahrscheinlich, dh ich habe eine falsche Annahme gemacht und das, mit dem ich verlinkt habe, nicht gelesen, wie ich es normalerweise herausgefunden habe von der automatischen Vervollständigung selbst). Hoffe, das Update behebt Ihren Punkt, lassen Sie mich wissen, wenn es nicht tut
Ruben Bartelink
59

Hier ist ein Beispiel für die Verwendung eines Rückrufs zum Testen einer Entität, die an einen Datendienst gesendet wurde, der eine Einfügung verarbeitet.

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

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

Alternative generische Methodensyntax:

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

Dann können Sie so etwas testen

Assert.AreEqual("test", insertedEntity.Description, "Wrong Description");
Jeff Hall
quelle
4
In diesem speziellen Fall (abhängig davon, ob Sie versuchen, Tests gegen den Status oder das Verhalten auszudrücken) ist es in einigen Fällen möglicherweise sauberer, einen Test It.Is<T>in einem zu verwenden, Mock.Verifyanstatt den Test mit Zeitarbeitskräften zu verschmutzen. Aber +1, weil ich wette, dass es viele Leute gibt, die anhand eines Beispiels am besten funktionieren.
Ruben Bartelink
10

Es gibt zwei Arten von Callbackin Moq. Eine passiert, bevor der Anruf zurückkehrt; Das andere geschieht, nachdem der Anruf zurückgekehrt ist.

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

In beiden Rückrufen können wir:

  1. Überprüfen Sie die Methodenargumente
  2. Argumente für Erfassungsmethoden
  3. Kontextzustand ändern
Shaun Luttin
quelle
2
Tatsächlich geschehen beide, bevor der Anruf zurückkehrt (soweit es den Anrufer betrifft). Siehe stackoverflow.com/a/28727099/67824 .
Ohad Schneider
5

Callbackist einfach ein Mittel, um einen beliebigen benutzerdefinierten Code auszuführen, wenn eine der Methoden des Mocks aufgerufen wird. Hier ist ein einfaches Beispiel:

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

Ich bin kürzlich auf einen interessanten Anwendungsfall gestoßen. Angenommen, Sie erwarten einige Anrufe bei Ihrem Mock, die jedoch gleichzeitig stattfinden. Sie haben also keine Möglichkeit, die Reihenfolge zu kennen, in der sie angerufen werden, aber Sie möchten wissen, welche Anrufe Sie erwartet haben (unabhängig von der Reihenfolge). Sie können so etwas tun:

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

Übrigens, lassen Sie sich nicht durch die irreführende Unterscheidung "vor Returns" und "nach Returns" verwirren . Es ist lediglich eine technische Unterscheidung, ob Ihr benutzerdefinierter Code nach Returnsoder vor der Auswertung ausgeführt wird. In den Augen des Anrufers werden beide ausgeführt, bevor der Wert zurückgegeben wird. In der Tat, wenn die Methode voidzurückkehrt, können Sie nicht einmal aufrufen Returnsund dennoch funktioniert es genauso. Weitere Informationen finden Sie unter https://stackoverflow.com/a/28727099/67824 .

Ohad Schneider
quelle
1

Zusätzlich zu den anderen guten Antworten hier habe ich damit Logik ausgeführt, bevor eine Ausnahme ausgelöst wurde. Zum Beispiel musste ich alle Objekte speichern, die zur späteren Überprüfung an eine Methode übergeben wurden, und diese Methode musste (in einigen Testfällen) eine Ausnahme auslösen. Der Aufruf .Throws(...)an Mock.Setup(...)Überschreibungen der Callback()Aktion und nie nennt. Wenn Sie jedoch eine Ausnahme innerhalb des Rückrufs auslösen, können Sie immer noch alle guten Dinge tun, die ein Rückruf zu bieten hat, und dennoch eine Ausnahme auslösen.

Frank Bryce
quelle