Mockito-Matcher sind statische Methoden und Aufrufe dieser Methoden, die bei Aufrufen von und für Argumente stehen .when
verify
Hamcrest-Matcher (archivierte Version) (oder Hamcrest-Matcher) sind zustandslose Allzweckobjektinstanzen, Matcher<T>
die eine Methode implementieren und matches(T)
verfügbar machen, die true zurückgibt, wenn das Objekt den Kriterien des Matchers entspricht. Sie sollen frei von Nebenwirkungen sein und werden im Allgemeinen in Behauptungen wie der folgenden verwendet.
/* Mockito */ verify(foo).setPowerLevel(gt(9000));
/* Hamcrest */ assertThat(foo.getPowerLevel(), is(greaterThan(9000)));
Es gibt Mockito-Matcher, die von Matchern im Hamcrest-Stil getrennt sind, sodass Beschreibungen übereinstimmender Ausdrücke direkt in Methodenaufrufe passen : Mockito-Matcher geben zurück, T
wo Hamcrest-Matcher-Methoden Matcher-Objekte (vom Typ Matcher<T>
) zurückgeben.
Mockito Matcher werden durch statische Methoden aufgerufen wie eq
, any
, gt
, und startsWith
auf org.mockito.Matchers
und org.mockito.AdditionalMatchers
. Es gibt auch Adapter, die sich in den Mockito-Versionen geändert haben:
- Bei Mockito 1.x handelt es sich
Matchers
bei einigen Aufrufen (z. B. intThat
oder argThat
) um Mockito-Matcher, die Hamcrest-Matcher direkt als Parameter akzeptieren. ArgumentMatcher<T>
erweitert org.hamcrest.Matcher<T>
, das in der internen Hamcrest-Darstellung verwendet wurde und eine Hamcrest-Matcher-Basisklasse anstelle eines Mockito-Matchers war.
- Für Mockito 2.0+ ist Mockito nicht mehr direkt von Hamcrest abhängig.
Matchers
ruft als intThat
oder argThat
umbrochene ArgumentMatcher<T>
Objekte auf, die nicht mehr implementiert org.hamcrest.Matcher<T>
sind, aber auf ähnliche Weise verwendet werden. Hamcrest-Adapter wie argThat
und intThat
sind noch verfügbar, wurden jedoch MockitoHamcrest
stattdessen geändert .
Unabhängig davon, ob die Matcher Hamcrest oder einfach Hamcrest-Stil sind, können sie wie folgt angepasst werden:
/* Mockito matcher intThat adapting Hamcrest-style matcher is(greaterThan(...)) */
verify(foo).setPowerLevel(intThat(is(greaterThan(9000))));
In der obigen Anweisung: foo.setPowerLevel
ist eine Methode, die eine akzeptiert int
. is(greaterThan(9000))
gibt a zurück Matcher<Integer>
, was als setPowerLevel
Argument nicht funktionieren würde . Der Mockito-Matcher intThat
umschließt diesen Matcher im Hamcrest-Stil und gibt einen zurück, int
damit er als Argument angezeigt werden kann . Mockito-Matcher gt(9000)
möchten den gesamten Ausdruck wie in der ersten Zeile des Beispielcodes in einen einzigen Aufruf einbinden.
Was Matcher tun / zurückgeben
when(foo.quux(3, 5)).thenReturn(true);
Wenn Sie keine Argumentvergleicher verwenden, zeichnet Mockito Ihre Argumentwerte auf und vergleicht sie mit ihren equals
Methoden.
when(foo.quux(eq(3), eq(5))).thenReturn(true); // same as above
when(foo.quux(anyInt(), gt(5))).thenReturn(true); // this one's different
Wenn Sie einen Matcher wie any
oder gt
(größer als) aufrufen , speichert Mockito ein Matcher-Objekt, das Mockito veranlasst, diese Gleichheitsprüfung zu überspringen und das Match Ihrer Wahl anzuwenden. Im Falle argumentCaptor.capture()
speichert es einen Matcher, der sein Argument stattdessen für eine spätere Überprüfung speichert.
Matcher geben Dummy-Werte wie Null, leere Sammlungen oder zurück null
. Mockito versucht, einen sicheren, geeigneten Dummy-Wert zurückzugeben, z. B. 0 für anyInt()
oder any(Integer.class)
oder leer List<String>
für anyListOf(String.class)
. Aufgrund der Typlöschung fehlen Mockito jedoch Typinformationen , um einen beliebigen Wert außer null
für any()
oder zurückzugeben argThat(...)
, was zu einer NullPointerException führen kann, wenn versucht wird, einen null
primitiven Wert automatisch zu entpacken .
Matcher mögen eq
und gt
nehmen Parameterwerte; Idealerweise sollten diese Werte berechnet werden, bevor das Stubbing / Verifizieren beginnt. Das Aufrufen eines Mocks während des Verspottens eines anderen Anrufs kann das Stubbing beeinträchtigen.
Matcher-Methoden können nicht als Rückgabewerte verwendet werden. Es gibt zum Beispiel keine Möglichkeit, thenReturn(anyInt())
oder thenReturn(any(Foo.class))
in Mockito zu formulieren. Mockito muss genau wissen, welche Instanz bei Stubbing-Aufrufen zurückgegeben werden soll, und wird keinen beliebigen Rückgabewert für Sie auswählen.
Implementierungsdetails
Matcher werden (als Objekt-Matcher im Hamcrest-Stil) in einem Stapel gespeichert, der in einer Klasse namens ArgumentMatcherStorage enthalten ist . MockitoCore und Matchers besitzen jeweils eine ThreadSafeMockingProgress- Instanz, die statisch eine ThreadLocal enthält, die MockingProgress-Instanzen enthält. Es ist dieses MockingProgressImpl , das ein konkretes ArgumentMatcherStorageImpl enthält . Folglich ist der Mock- und Matcher-Status statisch, aber zwischen den Klassen Mockito und Matchers konsistent.
Die meisten Matcher nur Anrufe zu diesem Stapel hinzufügen, mit einer Ausnahme für Matcher wie and
, or
undnot
. Dies entspricht perfekt der Bewertungsreihenfolge von Java (und stützt sich darauf) , die Argumente von links nach rechts bewertet, bevor eine Methode aufgerufen wird:
when(foo.quux(anyInt(), and(gt(10), lt(20)))).thenReturn(true);
[6] [5] [1] [4] [2] [3]
Dieser Wille:
- In
anyInt()
auf den Stapel.
- In
gt(10)
auf den Stapel.
- In
lt(20)
auf den Stapel.
- Entfernen
gt(10)
und lt(20)
hinzuzufügen and(gt(10), lt(20))
.
- Aufruf
foo.quux(0, 0)
, der (sofern nicht anders angegeben) den Standardwert zurückgibt false
. Intern markiert Mockito quux(int, int)
als den letzten Anruf.
- Call
when(false)
, der sein Argument verwirft und sich auf quux(int, int)
die in 5 identifizierte Stub-Methode vorbereitet . Die einzigen zwei gültigen Zustände haben die Stapellänge 0 (Gleichheit) oder 2 (Matcher), und es gibt zwei Matcher auf dem Stapel (Schritte 1 und 4) Mockito stoppt die Methode mit einem any()
Matcher für das erste Argument und and(gt(10), lt(20))
für das zweite Argument und löscht den Stapel.
Dies zeigt einige Regeln:
Mockito kann den Unterschied zwischen quux(anyInt(), 0)
und nicht erkennen quux(0, anyInt())
. Sie sehen beide aus wie ein Aufruf quux(0, 0)
mit einem int Matcher auf dem Stapel. Wenn Sie also einen Matcher verwenden, müssen Sie alle Argumente abgleichen.
Anrufreihenfolge ist nicht nur wichtig, sondern macht das alles möglich . Das Extrahieren von Matchern zu Variablen funktioniert im Allgemeinen nicht, da normalerweise die Aufrufreihenfolge geändert wird. Das Extrahieren von Matchern zu Methoden funktioniert jedoch hervorragend.
int between10And20 = and(gt(10), lt(20));
/* BAD */ when(foo.quux(anyInt(), between10And20)).thenReturn(true);
// Mockito sees the stack as the opposite: and(gt(10), lt(20)), anyInt().
public static int anyIntBetween10And20() { return and(gt(10), lt(20)); }
/* OK */ when(foo.quux(anyInt(), anyIntBetween10And20())).thenReturn(true);
// The helper method calls the matcher methods in the right order.
Der Stapel wechselt oft genug, so dass Mockito ihn nicht sehr sorgfältig überwachen kann. Es kann den Stapel nur überprüfen, wenn Sie mit Mockito oder einem Mock interagieren, und muss Matcher akzeptieren, ohne zu wissen, ob sie sofort verwendet oder versehentlich aufgegeben werden. Theoretisch sollte der Stapel außerhalb eines Aufrufs von when
oder immer leer sein verify
, aber Mockito kann dies nicht automatisch überprüfen. Sie können manuell überprüfen mit Mockito.validateMockitoUsage()
.
Bei einem Aufruf von when
ruft Mockito tatsächlich die betreffende Methode auf, die eine Ausnahme auslöst, wenn Sie die Methode zum Auslösen einer Ausnahme gestoppt haben (oder Werte ungleich Null oder ungleich Null benötigen).
doReturn
und doAnswer
(etc) rufen nicht die eigentliche Methode auf und sind oft eine nützliche Alternative.
Wenn Sie mitten im Stubbing eine Scheinmethode aufgerufen hätten (z. B. um eine Antwort für einen eq
Matcher zu berechnen ), würde Mockito stattdessen die Stapellänge anhand dieses Aufrufs überprüfen und wahrscheinlich fehlschlagen.
Wenn Sie versuchen, etwas Schlechtes zu tun, z. B. eine endgültige Methode zu stubben / zu überprüfen , ruft Mockito die echte Methode auf und lässt zusätzliche Matcher auf dem Stapel . Der final
Methodenaufruf löst möglicherweise keine Ausnahme aus, aber Sie erhalten möglicherweise eine InvalidUseOfMatchersException von den streunenden Matchern, wenn Sie das nächste Mal mit einem Mock interagieren.
Allgemeine Probleme
InvalidUseOfMatchersException :
Stellen Sie sicher, dass jedes einzelne Argument genau einen Matcher-Aufruf enthält, wenn Sie überhaupt Matcher verwenden, und dass Sie keinen Matcher außerhalb eines when
oder verify
-Aufrufs verwendet haben. Matcher sollten niemals als gestoppelte Rückgabewerte oder Felder / Variablen verwendet werden.
Stellen Sie sicher, dass Sie kein Mock aufrufen, um ein Matcher-Argument bereitzustellen.
Stellen Sie sicher, dass Sie nicht versuchen, eine endgültige Methode mit einem Matcher zu stub / verifizieren. Es ist eine großartige Möglichkeit, einen Matcher auf dem Stapel zu belassen. Wenn Ihre endgültige Methode keine Ausnahme auslöst, ist dies möglicherweise das einzige Mal, dass Sie feststellen, dass die Methode, über die Sie sich lustig machen, endgültig ist.
NullPointerException mit primitiven Argumenten: (Integer) any()
gibt null zurück, während any(Integer.class)
0 zurückgegeben wird; Dies kann dazu führen, NullPointerException
dass Sie eine int
anstelle einer Ganzzahl erwarten . In jedem Fall bevorzugen anyInt()
, die Null zurückgeben und auch den Auto-Boxing-Schritt überspringen.
Nullpointer oder andere Ausnahmen: Anrufe in denen when(foo.bar(any())).thenReturn(baz)
tatsächlich nennen foo.bar(null)
, die Sie stubbed haben könnten eine Ausnahme ausgelöst , wenn ein Null - Argument zu empfangen. Beim Umschalten auf doReturn(baz).when(foo).bar(any())
überspringt das Stubbed-Verhalten .
Allgemeine Fehlerbehebung
Verwenden Sie MockitoJUnitRunner oder rufen validateMockitoUsage
Sie explizit Ihre tearDown
oder @After
-Methode auf (was der Läufer automatisch für Sie tun würde). Auf diese Weise können Sie feststellen, ob Sie Matcher missbraucht haben.
Fügen Sie zum Debuggen Aufrufe validateMockitoUsage
direkt zu Ihrem Code hinzu. Dies wird ausgelöst, wenn Sie etwas auf dem Stapel haben, was eine gute Warnung vor einem schlechten Symptom ist.