Mockito Teil 1

Lesezeit: 8 Minuten

Mockito ist eine Java-Bibliothek um Objekte zu mocken und für mich ein unverzichtbares Werkzeug beim Schreiben von Tests. Ein Mock ist eine Attrappe eines Objektes. Mockito erzeugt ein solches durch Ableitung. Eine Unit mit komplexen Abhängigkeiten lässt sich wunderbar isoliert testen, indem die Abhängigkeiten durch Mocks ersetzt werden.

Da ich zu Mockito viel zu schreiben habe, habe ich mich entschieden, den Artikel in zwei Teilen zu veröffentlichen. Der zweite Teil kann über diesen Link erreicht werden. In diesem ersten Teil gehe ich auf die Basics von Mockito ein:

  • Wie man ein Objekt mockt
  • Wie man Verhalten mockt
  • Wie Mockito Assertions unterstützt

Die Methode mock()

Einen Mock zu erzeugen, ist super einfach:

Car car = mock(Car.class);

Die Methode mock() ist statisch und wird üblicherweise als import static importiert. Sie gehört zur Klasse Mockito. Der Aufruf im Code-Beispiel oben erzeugt eine leere Hülle der Klasse Car. Die Mock-Instanz gibt standardmäßig für alle Aufrufe den Default-Wert zurück, den man auch erhält, wenn man einem Feld einer Klasse nicht explizit einen Wert zuweist, d.h. null für Objekte, false für booleans, 0 für int und so weiter.

Die Methode when()

Die Properties der Mock-Instanz lassen sich zusätzlich mocken, so dass sich eine gemockte Abhängigkeit in einem Unit-Test wie gewünscht verhält. Die folgende Anweisung führt dazu, dass das gemockte Auto 5 Sitzplätze hat:

when(car.getNumberOfSeats()).thenReturn(5);

when() ist ebenfalls eine statische Methode der Klasse Mockito. Der in when() gemockte Methodenaufruf kann auch parametrisiert sein. Dabei können normale Variablen oder auch sogenannte Matcher angegeben werden, dazu weiter unten mehr.

// normale Argumente
when(car.calcMilesLeftForGivenPetrolRemaining(liters, unit)).thenReturn(330);

// Matcher
when(car.calcMilesLeftForGivenPetrolRemaining(eq(20), same(unit))).thenReturn(330);

Im ersten Aufruf werden die Variablen liters und units an die Methode übergeben. Der Mock wird damit angewiesen genau dann den Wert 330 zurückzugeben, wenn der Mock mit genau diesen Argumenten aufgerufen wird.

ArgumentMatchers

Manchmal haben wir im Kontext des Unit-Tests nicht Zugriff auf alle Objekte, die an eine zu mockende Methode übergeben werden. Dann kann man mit ArgumentMatchern arbeiten. Der Matcher eq() steht für einen Vergleich mit equals(), der Matcher same() für einen Vergleich gegen die Referenz ( == ). Matcher und normale Argumente lassen sich nicht vermischen. Wenn man für ein Argument auf einen Matcher angewiesen ist, dann muss man alle Argumente als Matcher angeben.

Ein weiterer sehr populärer Matcher ist any(), den es auch in der Variante any(Class<T>) gibt sowie als sprechende Variante für bestimmte oft verwendete Typen, etwa anyString() und anyLong(). Grundsätzlich würde ich immer versuchen, die Matcher so spezifisch wie möglich zu halten. Wenn man auf ein Objekt aber keinen Zugriff hat, ein Vergleich über equals() keinen Sinn ergibt, weil es sich um eine Service-Klasse handelt oder der Parameter für den Testkontext irrelevant ist, dann ist any() super um einfach alles zu erschlagen. Um ein Null-Argument zu erfassen, gibt es übrigens einen eigenen Matcher: isNull().

Wiederholte Aufrufe gemockten Verhaltens

Mockito erlaubt es auch für wiederholte Aufruf eine Reihe verschiedener Rückgabewerte zu definieren. Dafür nimmt thenReturn() grundsätzlich einen Varargs entgegen. Diese Flexibilität ermöglicht es z.B. zu simulieren, dass die Austrittsbedingung in einer while-Schleife nach dem dritten Durchlauf erfüllt ist.

Es gibt übrigens nicht nur thenReturn(). Mit thenThrow(Throwable) kann man eine gemockte Methode anweisen, eine Exception zu werfen. Damit lassen sich auch die Edge Cases testen: Wie verhält sich die Unit, wenn das Schreiben in eine Datei fehlschlägt oder keine Verbindung zur Datenbank aufgebaut werden kann? Mit thenCallRealMethod() hingegen lässt sich der tatsächliche Code der Methode einer gemockten Instanz aufrufen.

void-Methoden mocken

Möchte man Methoden ohne Rückgabewert mocken, so gilt eine leicht andere Syntax:

doCallRealMethod().when(car).park();
doThrow(new RuntimeException()).when(car).park();

Der wichtigste Unterschied ist, dass nicht der Aufruf der Methode im when() gekapselt wird, da diese Methode ja keinen Rückgabewert hat, sondern dass nur die Referenz auf den Mock in when() gegeben wird und die zu mockende Methode als Aufruf dahintergesetzt wird.

UnnecessaryStubbingException

Mockito ist übrigens per Default strikt bei der Verwendung gemockter Aufrufe. Wird ein Aufruf gemockt, aber im Test nicht aufgerufen, wirft Mockito eine UnnecessaryStubbingException und lässt damit den Test fehlschlagen. Das ist sehr gut, da dies in der Regel darauf hindeutet, dass der Test fehlerhaft ist und die Mocks nicht wie erwartet verwendet werden. In wenigen Fällen kann es aber sein, dass man mehr mocken möchte, als tatsächlich im Test verwendet wird. Dann kann man den unparametrisierten Aufruf lenient() vor das when() stellen. Bei einem solchen Aufruf wird Mockito nicht bemängeln, wenn das gemockte Verhalten im Test nicht verwendet wird. Bei dem when() hinter dem lenient() handelt es sich übrigens um ein anderes when(), es stammt nämlich aus der Klasse LenientStubber, ist aber von der Verwendung her identisch zu dem when() der Klasse Mockito.

Lenient lässt sich über mock(Car.class, withSettings().lenient()) auch auf die gesamte Mock-Instanz oder mit @MockitoSettings(strictness = Strictness.LENIENT) sogar auf die ganze Testmethode oder -klasse anwenden.

Die Methode verify()

Mockito kann nicht nur Objekte und ihr Verhalten mocken, sondern auch Assertions unterstützen. Dazu fünf Beispiele:

verify(car).park();
verify(car).calcMilesLeftForGivenPetrolRemaining(eq(20), any(Unit.class));
verify(car, never()).brake();
verify(car, times(5)).accelerate();
verifyNoInteractions(garage);

…und hier die Erläuterungen dazu:

  • Im ersten Aufruf wird verifiziert, dass die void-Methode park() an der Mock-Instanz genau einmal aufgerufen wurde.
  • Im zweiten Aufruf wird verifiziert, dass die parametrisierte Methode calc...() mit einem int equals(20) und einer beliebigen Instanz der Klasse Unit genau einmal aufgerufen wurde.
  • Im dritten Aufruf wird verifiziert, dass die Methode brake() an der gemockten Instanz niemals aufgerufen wurde.
  • Im vierten Aufruf wird verifiziert, dass an der gemockten Instanz die Methode accelerate() genau 5 Mal aufgerufen wurde.
  • Im letzten Aufruf wird verifiziert, dass in dem Test kein einziges Mal mit der Mock-Instanz garage interagiert wurde.

Der letzte Aufruf eignet sich wunderbar, um in einem Test zu demonstrieren, dass aufgrund einer Exception oder nicht erfüllten Vorbedingung der restliche Programmablauf nicht mehr abgespielt und damit z.B. nicht mit einer Datenbankverbindung interagiert wird.

verify() ist eine Assertion und unterscheidet sich damit von der Intention her von den anderen Mockito-Features: mock() und when() simulieren Verhalten, damit komplexe Abhängigkeiten ausgeblendet werden können oder sich wie erwünscht verhalten, um verschiedene Anwendungsfälle durchzutesten. verify() überprüft eine Annahme und lässt den Test fehlschlagen, wenn diese nicht erfüllt wurde.

Einschränkungen

Die Grenzen von Mockito bestehen in den Grenzen der Vererbung in Java. Da Mockito seine Mocking-Funktionalität durch Ableitungen realisiert, können als final deklarierte Klassen und Methoden nicht gemockt werden. Auch das Mocken von statischen Methoden ist nicht möglich, da diese nicht der Vererbung unterliegen. Abhilfe schaffen Tools wie PowerMock, die den Bytecode manipulieren um solche Klassen und Methoden zu mocken, oder man versucht auf statische Methoden und Methoden/Klassen mit dem Modifier final zu verzichten. Statische Methoden aus dem JDK lassen sich über einen Wrapper mocken, der die statischen Aufrufe in Instanzmethoden kapselt. Für finale Methoden/Klassen kann man ebenfalls auf eine Wrapper-Klasse zurückgreifen, die alle Aufrufe dann an die finale Klasse delegiert, und dann nur die Wrapper-Klasse testen. Schön ist das nicht, aber finale Methoden/Klassen sind ja zum Glück die Ausnahme.

- Offizielle Webseite von Mockito