Oracle Java: JUnit Testfall mit Mockito

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ück geben. 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ück liefert. 

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ück geliefert 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 initalisieren. 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.

JUnit Testfall mit Mockito - Fehlermeldung
JUnit Testfall mit Mockito – Fehlermeldung

Warum wird das Feld „message“ mit null initialisiert? 
Das liegt daran das 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ück liefert.

@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, das Mockito nur „public“ Methoden verarbeiten kann. D.h. wir müssen beim Design unserer Implementation darauf achten das 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) drei mal 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());		
	
}

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üßte man auf ein zusätzliches Framework wie EasyMock oder PowerMock zurückgreifen.

 

 

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.