Mocks von injizierten Abhängigkeiten
Um nun einzelne Objekte zu mocken, könnte man den Typ dieser Objekte ableiten (oder erweitern) und anschließend das gewünschte Verhalten implementieren.
Folgende Beispielsituation: Wir haben einen Bäcker, der Brot backen möchte und sich dafür eines Ofens bedient.
public class Baker { @Autowired private BakingOven oven; public void breadBaking() { mixAllIngredients(); putInOven(); // Zugriff auf die abhängige Komponente oven.baking(220, 1800); putOutOfOven(); } private void putOutOfOven() { ... } private void putInOven() { ... } private void mixAllIngredients() { ... } }
Der Bäcker ist also vom Ofen abhängig. In obigem Beispiel wird unter anderem Dependency Injection durch Spring benutzt, weil unser Bäcker eben Bäcker und kein Techniker ist und deshalb den Ofen nicht neubauen möchte, bevor er ihn benutzt. Er übergibt also die Verantwortung der Instanziierung ans Spring Framework, weshalb wir dieses Pattern häufig auch mit Inversion of Control verbinden.
Wenn wir nun einen Unit Test für den Bäcker schreiben, so wollen wir doch die Funktionsfähigkeit des Ofens voraussetzen und nicht mit unserem Unit Test mittesten.
Der eine oder andere mag nun denken, aber wie sollen wir testen, ob wir (leckeres) Brot backen können, wenn wir den Ofen nicht mittesten? Berechtigte Frage. Diese wird allerdings durch Integrationtests beantwortet und NICHT durch Unit Tests.
Kommt sofort die nächste Frage: Warum schreiben wir dann nicht einfach Integrationtests statt Unit Tests? Einfache Antwort. Zum einen benötigen Integrationtests deutlich mehr Laufzeit als Unit Tests. Zum zweiten haben wir durch unser Klassenkonstrukt abgeschlossene Einheiten, Units, die für sich genommen, ein bestimmtes Verhalten implementieren.
Wie jemand anderes unsere Komponente verwendet, entzieht sich häufig unserer Kenntnis. Aber so wie es dem Ofenhersteller egal ist, ob wir Pizza oder Brot backen, so müssen auch wir unserer Komponente eine gewisse Stabilität zukommen lassen. Deshalb schreiben wir für alle Komponenten Unit Tests.
Nichtsdestotrotz ruft unsere Bäcker-Implementierung eine Methode der Ofen-Komponente auf. Wir müssen uns also eine Attrappe für den Ofen bauen, die sich so verhält wie der Ofen selbst – zumindest nach außen hin. Vermutlich wird diese Attrappe das Brot nicht backen.
Um dies für Spring Abhängigkeiten zu tun, verwenden wir Mockito:
package com.encoway.spring; import static org.mockito.Mockito.verify; import org.junit.Test; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.MockitoAnnotations; public class BakerTest { @Mock private BakingOven oven; @InjectMocks private Baker baker; @Test public void testBreadBaking() throws Exception { MockitoAnnotations.initMocks(this); baker.breadBaking(); verify(oven).baking(220, 1800); } }
In Zeile 12/13 deklarieren wir den Ofen als Mock (Attrappe).
In Zeile 15/16 deklarieren wir unser zu testendes Objekt derart, dass es von Mockito alle Abhängigkeiten als Mocks injiziert bekommt.
In Zeile 20 initialisieren wir die Dependency Injection durch Mockito. Als Hinweis sei hier erwähnt, dass die initMocks Methode mit dem
Test selbst erstellt wird.
Alternativ ging auch der folgende Klassenheader:
package com.encoway.spring; import static org.mockito.Mockito.verify; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.runners.MockitoJUnitRunner; @RunWith(MockitoJUnitRunner.class) public class BakerTest { @Mock private BakingOven oven; @InjectMocks private Baker baker; @Test public void testBreadBaking() throws Exception { baker.breadBaking(); verify(oven).baking(220, 1800); } }
Wie man an diesem einfachen Test sieht, können wir durch den verify-Aufruf in Zeile 23 sicherstellen, dass unser Ofen korrekt benutzt wurde. Ob der Ofen mit diesem Einstellungen tut, was er tun soll, prüft der Unit Test des Ofens.