Mockito – Vorstellung des Mocking-Frameworks

Mockito – 1 – Einführung

Vorwort

Dies ist der erste Artikel zum Unit-Testing Framework für Java – „Mockito“ im Journal.
Daher soll zunächst eine kurze Übersicht über das Thema „Mockito“ gegeben werden.
Es wird jedoch keinen Vergleich mit anderen Mocking-Frameworks geben, da dies den Rahmen sprengen würde.
Auch wird kein Anspruch auf Vollständigkeit erhoben, da hier der Fokus auf dem „schnellen Einstieg“ und „kurzer Überblick“ liegt.

Mockito

Mockito ist eines der verbreitetsten Unit-Testing Framework für Java.
Nach eigenen Aussagen:

Massive StackOverflow community voted Mockito the best mocking framework for java.

 

Dan North, the originator of Behavior-Driven Development wrote this back in 2008:
“We decided during the main conference that we should use JUnit 4 and Mockito because we think they are the future of TDD and mocking in Java”

Es bietet Möglichkeiten zur Vorbereitung und Durchführung von Unit-Tests, sowie zum nachträglichen Überprüfen der durchgeführten Aktionen auf den Mocks und der Ergebnisse eines Testdurchlaufs.
Eine Leistung des Frameworks besteht darin, die zu testende Unit (eine Komponente im allgemeinen programmiertechnischen Sinne) möglichst umfassend von den äußeren Schnittstellen zu lösen und für sich testen zu können.
Das Ziel ist somit, eine Komponente als Black-Box zu betrachten und nur noch das Verhalten an den Schnittstellen zu definieren bzw. zu kontrollieren – d.h. Input und Output.
Allgemein kann gesagt werden: Inputs werden definiert, die Outputs überprüft.
Andere Komponenten, mit denen eventuell während des Tests (intern) kommuniziert wird, werden hierbei „gemockt“, also durch einen in seinem relevanten Verhalten exakt definierten Baustein („Mock“) ausgetauscht. Die Kontrolle, ob bestimmte Funktionen eines gemockten Objektes aufgerufen wurden, zählt also ebenso zu der Leistung von Mockito.

Leistungen von Mockito

  • Abtrennung der getesteten Unit von der Umgebung
  • Definition des Laufzeitverhaltens von Mocks (Methoden-Stubbing)
  • Mock von beliebigen Objekten erstellen (Mocken auf Basis von Interfaces oder abstrakten Klassen möglich)
  • Unterstützt Dependency-Injection (z.B. „Google Guice“, „Spring“)
  • Programmatisch oder über Verwendung von Annotations
  • Unterstützt Manipulationen per Reflection
  • Unterstützt Manipulationen von statischen Methoden
  • Verschiedene Dialekte

Bezüglich des letzten Punktes kann noch ergänzt werden, dass es sich bei den Dialekten um dieselbe Funktionalität mithilfe unterschiedlicher Schreibweisen handelt.
Z.B. drückt sowohl

als auch

dasselbe aus. Es wird sogar intern auf dieselben Methoden referenziert, wordurch sich lediglich ein Unterschied in der Lesbarkeit ergibt.

Ein Beispiel der Anwendung

Gegeben sei ein Interface:

Dieses soll in einem Test als Mock beschrieben werden.
Gegeben sei also eine zu testende Implementierung, welche intern eine NumberService-Implementierung verwendet.

Zu beachten ist hier, dass die Instanzen über Dependency Injection zugewiesen werden.
In diesem Fall wird die „@Autowired“-Annotation des Spring-Frameworks eingesetzt.
Die Test-Class Implementierung hierfür wird im Folgenden gezeigt.

Durch die hier gezeigte @Mock Annotation wird eine neue Instanz bereitgestellt, welche das angegebene Interface (hier NumberService) implementiert bzw. eine abstrakte / konkrete Klasse repräsentiert. Diese Instanz wird dynamisch durch Mockito erzeugt und bietet im Mockito-Testkontext die Möglichkeit, den erwarteten Anforderungen innerhalb des Tests entsprechend angepasst zu werden. Andernfalls zeigen alle nicht im Verhalten vordefinierten Methoden des Mocks ein Standardverhalten.

Standardverhalten der Methoden

  • Rückgabewerte von Methoden mit komplexen Rückgabe-Typen: NULL
  • Bei Zahlentypen und auch Wrapper-Typen wie Double, Integer, Long, etc.: 0

Wie im Beispiel zu erkennen, ist eine weitere Aktion notwendig, bevor auch tatsächlich die mit @Mock annotierten Objekte in dem mit @InjectMocks annotierten Objekt initialisiert werden.
Die @BeforeMethod-Annotation des Test-Frameworks wird vor jeder Ausführung einer Test-Methode ausgeführt. Dies ist abhängig vom Test-Framework.
Hier wird per:

die Initialisierung aller Objekte innerhalb der Testklassen-Instanz bewirkt.
Alle Felder, welche mit der @Mock-Annotation versehen sind, werden zugewiesen und in den statischen Test-Kontext geladen.
Alle im öffentlichen, statischen Test-Kontext befindlichen Mocks werden der Unit-Under-Test (UUT), zugewiesen. Diese wird als einziges Feld der Testklasse mit @InjectMocks annotiert.
Aufgrund der Notwendigkeit des Ladens der Mocks und des letztendlichen Einfügens der Mock-Instanzen in die UUT wird Mockito die Testklassen-Instanz zur Laufzeit übergeben.
Hier wird das eigentliche Test-Setup in der Laufzeit angelegt und alle Dependencies aufgelöst.
Es ist ebenso möglich innerhalb von Methoden Mocks dynamisch zu erstellen. Dies funktioniert über den folgenden Aufruf:

Das Beispiel zeigt die Zuweisung einer Mock-Instanz zu einer Variablen.

Erklärung des Beispiels

Der eigentliche Test verwendet in diesem Fall das TestNG Test-Framework.
Für den Test werden zunächst Testdaten angelegt. In diesem Fall ist dies das Array „inputValues“ mit den zu verarbeitenden Eingangswerten.
Da es sich um die Ermittlung eines Durchschnittswertes handelt, treten hier in Verbindung mit der Verwendung von Gleitkommazahlen bei der Division Rundungsungenauigkeiten auf.
Zu erwarten ist das Zwischenergebnis aus expectedCalculationResult = 2.0 / 3.0.
Der NumberService soll in diesem Fall innerhalb der UUT für die Rundung herangezogen werden.
Es ist also im Test-Setup zu erwarten, dass der Aufruf der Methode roundCurrency(…) mit dem Zwischenergebnis der Division als Übergabeparameter erfolgt.
Das definierte Verhalten wird über die folgende Zeile initialisiert:

Wenn also roundCurrency mit dem erwarteten ungerundeten Zwischenergebnis (expectedCalculationResult) aufgerufen wird, soll der Mock des NumberService das tatsächliche Ergebnis (expectedOutput) zurückgeben. Dieses muss nun nicht zwangsweise das korrekte Rundungsergebnis sein, da wir hierbei nur definieren:
„Wird die Methode des Mocks mit A als Argument aufgerufen, gebe B zurück und erwarte im Assert, dass B gleich der Ausgabe der getesteten Methode ist.“ Die einzige getätigte Festlegung ist, dass das interne Zwischenergebnis dem des Aufrufparameters entspricht (in diesem Fall exakt).
Somit ist die Funktionsweise innerhalb der getesteten Methode sichergestellt. Würde also intern der Aufruf geschehen, gäbe der Mock das Ergebnis zurück.
Doch sicherzustellen, dass der Aufruf auch tatsächlich passiert ist, ist ebenso wichtig, wie die Definition, was in dem Fall geschehen soll.
Man denke an Methoden, welche selbst keine Rückgaben besitzen, deren erfolgter Aufruf jedoch abgesichert werden muss.
Auch in diesem Fall ist ein Verifizieren von Methodenaufrufen notwendig.
Dies geschieht in der letzten Zeile des Tests:

Mockito wird also angewiesen den Aufruf der Methode roundCurrency zu verifizieren. Das obligatorische Argument ist hierbei expectedCalculationResult.
Fehlt der Aufruf, oder ist der Parameter inhaltlich nicht exakt derselbe, schlägt die Verifizierung fehl.
Man beachte, dass der Mock hier als Parameter für die verify-Methode übergeben wird, nicht – wie im Mockito.when – der gesamte Method-Call.

„Das Gleiche“ vs. „Dasselbe“ vs. „Ähnliches“ – Matchers und AdditionalMatchers

Manchmal ist die Exaktheit nicht so prioritiv, so dass eine Übereinstimmung einer Gleitkommazahl bis auf die z.B. sechste Stelle genügt.
Oder es bleiben Ungenauigkeiten in dem internen Aufruf bestehen, sodass lediglich definiert werden kann, um welchen Typ von Parameter es sich bei dem erwarteten Aufruf handeln wird.
In solchen Fällen, in denen die erwarteten Aufrufparameter nicht eindeutig sind, ist es möglich sowohl im „when“ als auch im „verify“ sog. Matchers zu verwenden.
Im Falle des Beispiels könnte mit

Matchers.any(Double.class) oder auch Matchers.any()

jeder Aufruf der Methode gemockt werden.

Hier wird definiert, dass der Aufruf der roundCurrency(Double value) Methode bei beliebigen Parametern getriggert werden soll.
D.h. bei jedem Aufruf der Methode auf diesem Mock würde expectedOutput returniert werden.
Gehen wir also davon aus, dass wir als Aufrufparameter den Wert 2.0 / 3.0 ˜ 0.666667 erwarten.
Die Definition von expectedCalculationResult = 0.666667 würde weder in

noch in

erfolgreich sein, denn 2.0 / 3.0 ? 0.666667.
Es bleibt eine Ungenauigkeit von rund |0.000001…|.
Hierfür können Matcher ebenfalls eingesetzt werden.
AdditionalMatchers ist die entsprechende Klasse, welche durch das Mockito-Framework bereitgestellt wird, um innerhalb von when und verify Ausdrücken erweiterte Vergleichsoperationen von tatsächlichen Parameterwerten zu machen.

Die „Accuracy“ wird über den zweiten Parameter im „eq„-Ausdruck angegeben. Dieser Wert definiert, wie groß die Abweichung des tatsächlichen Aufrufwertes von dem hier im ersten Parameter gegebenen Wert sein darf, um noch ein Triggern der hinterlegten Reaktion zu sein.
Dies ist lediglich ein Beispiel für die Einsatzfähigkeit dieser Matcher.

Schluss

Es handelt sich mit Mockito um ein mächtiges Framework, welches besonders im Bereich von TDD großes Potential besitzt.
Die Integration in bestehende Projekte und deren Testframeworks ist problemlos möglich.
In naher Zukunft sollen hier immer wieder Ergänzungen zu dem Thema folgen, welche auf die spezielleren Funktionen von Mockito eingehen werden.


Weiterführende Links:

http://mockito.org/

Verfasst von Klaus-Werner Heinrich am 15. September 2016