Wie funktioniert mockito when () Aufruf?


111

Angesichts der folgenden Mockito-Aussage:

when(mock.method()).thenReturn(someValue);

Wie geht Mockito vor, um ein Proxy für ein Mock zu erstellen, da die Anweisung mock.method () den Rückgabewert an when () übergibt? Ich stelle mir vor, dass dies einige CGLib-Sachen verwendet, wäre aber interessiert zu wissen, wie dies technisch gemacht wird.

Antworten:


118

Die kurze Antwort lautet, dass in Ihrem Beispiel das Ergebnis von mock.method()ein typgerechter leerer Wert ist. mockito verwendet die Indirektion über Proxy, Abfangen von Methoden und eine gemeinsam genutzte Instanz der MockingProgressKlasse, um zu bestimmen, ob ein Aufruf einer Methode auf einem Mock zum Stubben oder Wiedergeben eines vorhandenen Stubbed-Verhaltens dient, anstatt Informationen über das Stubbing über den Rückgabewert von zu übergeben eine verspottete Methode.

Eine Mini-Analyse in wenigen Minuten mit Blick auf den Mockito-Code lautet wie folgt. Beachten Sie, dass dies eine sehr grobe Beschreibung ist - hier sind viele Details im Spiel. Ich schlage vor, dass Sie die Quelle auf Github selbst überprüfen .

Wenn Sie eine Klasse mit der mockMethode der MockitoKlasse verspotten , geschieht im Wesentlichen Folgendes:

  1. Mockito.mockdelegiert an org.mockito.internal.MockitoCore.mock und übergibt die Standard-Mock-Einstellungen als Parameter.
  2. MockitoCore.mockdelegiert an org.mockito.internal.util.MockUtil.createMock
  3. Die MockUtilKlasse verwendet die ClassPathLoaderKlasse, um eine Instanz MockMakerzum Erstellen des Mocks abzurufen. Standardmäßig wird die CgLibMockMaker- Klasse verwendet.
  4. CgLibMockMakerverwendet eine von JMock entliehene Klasse, ClassImposterizerdie das Erstellen des Mocks übernimmt. Die Schlüsselelemente der verwendeten 'Mockito-Magie' sind die MethodInterceptorzum Erstellen des Mocks verwendeten: der Mockito MethodInterceptorFilterund eine Kette von MockHandler-Instanzen, einschließlich einer Instanz von MockHandlerImpl . Der Methoden-Interceptor übergibt Aufrufe an die MockHandlerImpl-Instanz, die die Geschäftslogik implementiert, die angewendet werden soll, wenn eine Methode für einen Mock aufgerufen wird (dh sucht, ob bereits eine Antwort aufgezeichnet wurde, und ermittelt, ob der Aufruf einen neuen Stub darstellt usw.). Der Standardstatus ist, dass ein typgerechter leerer Wert zurückgegeben wird, wenn für die aufgerufene Methode noch kein Stub registriert ist.

Schauen wir uns nun den Code in Ihrem Beispiel an:

when(mock.method()).thenReturn(someValue)

Hier ist die Reihenfolge, in der dieser Code ausgeführt wird:

  1. mock.method()
  2. when(<result of step 1>)
  3. <result of step 2>.thenReturn

Der Schlüssel zum Verständnis der Vorgänge liegt darin, was passiert, wenn die Methode auf dem Mock aufgerufen wird: Dem Methodenabfangjäger werden Informationen über den Methodenaufruf übergeben und an seine MockHandlerInstanzkette delegiert, an die schließlich delegiert wird MockHandlerImpl#handle. Währenddessen erstellt MockHandlerImpl#handleder Mock-Handler eine Instanz von OngoingStubbingImplund übergibt sie an die gemeinsam genutzte MockingProgressInstanz.

Wenn die whenMethode nach dem Aufruf von aufgerufen wird method(), delegiert sie an MockitoCore.when, wodurch die stub()Methode derselben Klasse aufgerufen wird . Diese Methode entpackt das laufende Stubbing aus der gemeinsam genutzten MockingProgressInstanz, in die der verspottete method()Aufruf geschrieben hat, und gibt es zurück. Dann wird die thenReturnMethode für die OngoingStubbingInstanz aufgerufen .


1
Danke für die ausführliche Antwort. Eine andere Frage - Sie erwähnen, dass "wenn die Methode nach dem Aufruf von method () aufgerufen wird" - woher weiß sie, dass der Aufruf von when () der nächste Aufruf (oder Wraps) des Aufrufs von method () ist? Hoffe das macht Sinn.
Marchaos

@marchaos Es weiß nicht. Wird mit der when(mock.method()).thenXyz(...)Syntax mock.method()im "Wiederholungs" -Modus ausgeführt, nicht im "Stubbing" -Modus. Normalerweise diese Ausführung mock.method()hat keine Wirkung, so später , wenn thenXyz(...)( thenReturn, thenThrow, thenAnswer, usw.) durchgeführt wird, geht es in Mode „Anstoßen“ , und zeichnet dann das gewünschte Ergebnis für diese Methode aufrufe.
Rogério

1
Rogerio, es ist tatsächlich etwas subtiler als das - Mockito hat keine expliziten Stubbing- und Wiederholungsmodi. Ich werde meine Antwort später bearbeiten, damit es klarer wird.
Paul Morie

Kurz gesagt, es ist einfacher, einen Methodenaufruf innerhalb einer anderen Methode mit CGLIB oder Javassist abzufangen, als beispielsweise den Operator "if" abzufangen.
Infeligo

Ich habe meine Beschreibung hier noch nicht massiert, aber ich habe sie auch nicht vergessen. Zu Ihrer Information.
Paul Morie

33

Die kurze Antwort lautet: Hinter den Kulissen verwendet Mockito eine Art globaler Variablen / Speicher, um Informationen zu Schritten zum Erstellen von Methodenstubs (Aufruf von method (), when (), thenReturn () in Ihrem Beispiel) zu speichern, damit dies schließlich möglich ist Erstellen Sie eine Karte darüber, was zurückgegeben werden soll, wenn was auf welchem ​​Parameter aufgerufen wird.

Ich fand diesen Artikel sehr hilfreich: Erläuterung der Funktionsweise von Proxy-basierten Mock Frameworks ( http://blog.rseiler.at/2014/06/explanation-how-proxy-based-mock.html ). Der Autor hat ein Demonstrations-Mocking-Framework implementiert. Ich fand eine sehr gute Ressource für Leute, die herausfinden möchten, wie diese Mocking-Frameworks funktionieren.

Meiner Meinung nach ist es eine typische Verwendung von Anti-Pattern. Normalerweise sollten wir 'Nebeneffekte' vermeiden, wenn wir eine Methode implementieren, was bedeutet, dass die Methode Eingaben akzeptieren und einige Berechnungen durchführen und das Ergebnis zurückgeben sollte - ansonsten hat sich nichts geändert. Aber Mockito verstößt nur absichtlich gegen diese Regel. Die Methoden speichern neben der Rückgabe des Ergebnisses eine Reihe von Informationen: Mockito.anyString (), mockInstance.method (), when (), thenReturn, alle haben spezielle Nebenwirkungen. Das ist auch der Grund, warum das Framework auf den ersten Blick wie eine Magie aussieht - normalerweise schreiben wir keinen solchen Code. Im Fall eines spöttischen Frameworks ist dieses Anti-Pattern-Design jedoch ein großartiges Design, da es zu einer sehr einfachen API führt.


4
Hervorragende Verbindung. Das Genie dahinter ist: Die sehr einfache API, mit der das Ganze sehr schön aussieht. Eine weitere gute Entscheidung ist, dass die when () -Methode Generika verwendet, sodass die thenReturn () -Methode typsicher ist.
David Tonhofer

Ich halte das für die bessere Antwort. Im Gegensatz zur anderen Antwort werden die Konzepte des Verspottungsprozesses anstelle des Kontrollflusses durch konkreten Code klar erläutert. Ich stimme zu, ausgezeichneter Link.
Mihca
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.