Mit dem Framework „Mockito“ kann man ein komplexes Objekt faken, bzw. Methoden und Objekte innerhalb einer Klasse nachbauen und für einen Testfall mit bestimmten Werten zurückgeben. Ganz nach dem Motto „Ich mocke mir die Welt, wie sie mir gefällt!“.
Erstellen eines Testprojektes in Eclipse
Erstellen wir uns für dieses Tutorial ein Testprojekt in der Entwicklungsumgebung Eclipse. Diese IDE kann unter https://www.eclipse.org/downloads/download.php?file=/technology/epp/downloads/release/2018-12/R/eclipse-jee-2018-12-R-win32-x86_64.zip heruntergeladen werden.
Hier nun ein leeres Eclipse Projekt mit allen benötigten Abhängigkeiten (dependencies) um dieses Tutorial durchzuarbeiten.
Einbinden von Mockito
Damit man das Framework „Mockito“ verwenden kann, muss man mindestens nachfolgende Abhängigkeit hinzufügen.
<dependency> <groupId>org.mockito</groupId> <artifactId>mockito-core</artifactId> <version>2.23.4</version> <scope>test</scope> </dependency>
JUnit Testfall erzeugen
Mit Mockito kann man bestehende Testfälle erweitern, d.h. wir benötigen zusätzlich die Abhängigkeiten für JUnit. Für dieses Tutorial verwende ich JUnit4.
<dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> <scope>test</scope> </dependency> <dependency> <groupId>org.hamcrest</groupId> <artifactId>hamcrest-core</artifactId> <version>2.1</version> <scope>test</scope> </dependency>
Erzeugen wir uns zunächst einen einfachen Testfall.
Wir haben hier nun eine einfache innere Klasse „SimpleClass“ mit einem Feld „message“ vom Typ String, mit dem Inhalt „Hello World!“.
class SimpleClass{ private String message = "Hello World!"; public String getMessage() { return message; } public void setMessage(String message) { this.message = message; } }
Nun können wir in der Methode „testSimpleClass“ testen, ob die Methode „getMessage“ im Standardfall den Text „Hello World!“ zurückliefert.
package de.draegerit.testmockito; import static org.junit.Assert.assertEquals; import org.junit.Test; public class TestSimpleClass { @Test public void testSimpleClass() { SimpleClass s1 = new SimpleClass(); assertEquals( "Hello World!", s1.getMessage()); } }
Nun können wir mit Mockito die Klasse „SimpleClass“ manipulieren so dass im Standardfall ein anderer Text zurückgeliefert wird.
Dazu müssen wir jedoch aus dieser Klasse in MockObjekt erzeugen.
Dieses können wir über eine Annotation machen, dann müssen wir jedoch zusätzlich vor dem Aufruf die Annotationen initialisieren. Dieses machen wir in einer Funktion welche mit @Before annotiert wurde. Diese „setup“ Funktion wird vor jedem Aufruf eines Testfalls in der TestKlasse ausgeführt.
@Mock private SimpleClass simpleClass; @Before public void setup() { MockitoAnnotations.initMocks(this); } @Test public void testSimpleClassMockAnnotation() { Mockito.when(simpleClass.getMessage()).thenReturn("Test123"); assertEquals( "Hello World!", simpleClass.getMessage()); }
Möchte man das ganze etwas leichtgewichtiger machen, so kann man auch innerhalb der Funktion ein MockObjekt erzeugen:
@Test public void testSimpleClassMock() { SimpleClass s1 = Mockito.mock(SimpleClass.class); assertEquals( "Hello World!", s1.getMessage()); }
Wenn wir nun die beiden Testfälle ausführen würden, so würde eine Exception auftreten.
Warum wird das Feld „message“ mit null initialisiert?
Das liegt daran, dass Mockito eine Hülle der Klasse erzeugt und die Felder zunächst mit den Standardeigenschaften erstellt. D.h. ein komplexer Datentyp wie java.lang.String wird mit null initialisiert, wenn wir diese Klasse mit der Funktion „mock“ oder Annotation „@Mock“ erzeugen.
Um hier die Standardimplementation der Klasse nutzen und testen zu können müssen wir die Klasse „SimpleClass“ statt mit einem „spy“ versehen.
@Spy SimpleClass simpleClass; @Before public void setup() { MockitoAnnotations.initMocks(this); }
Zusätzlich müssen wir die innere Klasse „SimpleClass“ in eine „public“ Class auslagern.
public class SimpleClass { private String message = "Hello World!"; public String getMessage() { return message; } public void setMessage(String message) { this.message = message; } }
Nun können wir unseren Testfall auf die Funktion „getMessage“ ausprogrammieren und prüfen, ob die Funktion im Standardfall den Text „Hello World!“ zurückliefert.
@Test public void test() { assertEquals( "Hello World!", simpleClass.getMessage()); }
Jedoch haben wir es selten mit so einfachen Testklassen Zutun.
Aufrufe prüfen
Mit Mockito kann man prüfen, ob eine Funktion auf einem MockObjekt ausgeführt wurde und zusätzlich definieren wie oft diese ausgeführt werden muss / sollte.
Nehmen wir an wir haben einen Datenbankservice, welcher uns eine Liste an Benutzer liefern soll. Wir wollen sicherstellen das dieser Service genau 1x eine Datenbankverbindung erstellt und dann die Benutzer lädt.
public class TestDatabaseAccess { @Spy private BenutzerDAO mockBenutzerDao; @Before public void setup() { MockitoAnnotations.initMocks(this); } @Test public void testAccessDb() { mockBenutzerDao.findAll(); Mockito.verify(mockBenutzerDao, Mockito.atLeast(1)).createConnection(); } }
Problem dabei ist, dass Mockito nur „public“ Methoden verarbeiten kann. D.h. wir müssen beim Design unserer Implementation darauf achten, dass wir dieses Testen können.
Im nachfolgenden testen wir einmal, ob das Hinzufügen von 3 Elementen zu der ArrayList geklappt hat.
package de.draegerit.testmockito; import java.util.ArrayList; import org.junit.Before; import org.junit.Test; import org.mockito.Mockito; import org.mockito.MockitoAnnotations; import org.mockito.Spy; import static org.mockito.Matchers.*; public class TestList { @Spy private ArrayList<String> mockList; @Before public void setup() { MockitoAnnotations.initMocks(this); } @Test public void testAddEntry() { mockList.add("Test 1"); mockList.add("Test 2"); mockList.add("Test 3"); Mockito.verify(mockList, Mockito.atLeast(3)).add(anyString()); } }
Ebenso kann man auf dem gleichen Wege prüfen, ob diese Elemente wieder gelöscht wurden, d.h. ob die Funktion „remove“ mit irgendeinem Wert (anyInt) dreimal aufgerufen wurde.
@Test public void testRemoveEntry() { mockList.add("Test 1"); mockList.add("Test 2"); mockList.add("Test 3"); Mockito.verify(mockList, Mockito.atLeast(3)).add(anyString()); mockList.remove(0); mockList.remove(0); mockList.remove(0); Mockito.verify(mockList, Mockito.atLeast(3)).remove(anyInt()); }
Fertiges Projekt zum Download
Hier nun das gesamte Eclipseprojekt zum download.
Fazit
Mit dem Framework Mockito kann man sich Objekte zum Testen erstellen und diese mit bestimmten Werten und Funktionen belegen. Dieses macht die Erstellung von umfangreichen Testfällen sehr einfach. Jedoch stößt man sehr schnell an die grenzen denn private & static Methoden lassen sich mit Mockito nicht testen bzw. mocken. Hier müsste man auf ein zusätzliches Framework wie EasyMock oder PowerMock zurückgreifen.