JAdventure - Kurs zur Softwareentwicklung
Toggle Dark/Light/Auto mode Toggle Dark/Light/Auto mode Toggle Dark/Light/Auto mode Back to homepage

2.7. Unit-Tests mit Mocking

Bisher haben wir nur den Client implementiert aber haben dabei auf das Schreiben von Unit-Tests verzichtet. Dies müssen wir nun noch nachholen.

ImageStoreTest

Wenn wir die Klasse ImageStore testen wollen, dann stellen wir direkt fest: Diese Klasse benötigt einen ImageService. Wir könnten jetzt natürlich eine Instanz von ImageService erstellen und dann Tests aufbauen, die dann auf diesen ImageService zugreifen und z. B. Bilder aus dem resource-Ordner lädt. Das bedeutet aber, das Änderungen an ImageService auch Tests von ImageStore scheitern lässt. Die Fehlersuche würde dadurch unnötig verkompliziert.

Mocking

Die Lösung für diese Problematik nennt sich mocking. Wir testen die Klasse ImageStore nicht mit einer Instanz von ImageService. Stattdessen lassen wir ein sogenanntes Mock erzeugen und geben dann vor, was bei bestimmten Aufrufen zurückgegeben werden soll.

Um Mocking einsetzen zu können, müssen wir erst einmal eine Abhängigkeit einbinden. Dazu passen wir die pom.xml an:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.jadv</groupId>
    <artifactId>jadventure</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <!-- Application Properties -->
        <java.version>17</java.version>

        <!-- Dependency versions -->
        <gson.version>2.10</gson.version>
        <junit.version>5.9.2</junit.version>
        <lombok.version>1.18.24</lombok.version>
        <mockito.version>4.11.0</mockito.version>

        <!-- other properties -->
        <maven.compiler.source>17</maven.compiler.source>
        <maven.compiler.target>17</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <dependencies>
        <!-- Gson -->
        <dependency>
            <groupId>com.google.code.gson</groupId>
            <artifactId>gson</artifactId>
            <version>${gson.version}</version>
        </dependency>

        <!-- Lombok -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>${lombok.version}</version>
        </dependency>

        <!-- JUnit 5 -->
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter-engine</artifactId>
            <version>${junit.version}</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter-params</artifactId>
            <version>${junit.version}</version>
            <scope>test</scope>
        </dependency>

        <!-- Mockito -->
        <dependency>
            <groupId>org.mockito</groupId>
            <artifactId>mockito-core</artifactId>
            <version>${mockito.version}</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.mockito</groupId>
            <artifactId>mockito-junit-jupiter</artifactId>
            <version>${mockito.version}</version>
            <scope>test</scope>
        </dependency>
    </dependencies>
</project>

Wir haben jetzt zum einen die pom Datei besser strukturiert:

  • Die Versionen sind in Properties definiert worden, so dass es eine zentrale Stelle für Änderungen gibt.
  • Wir haben neben Mockito noch eine weitere JUnit-Library eingebunden, die für parametrisierte Tests notwendig ist.

Nun können wir einen ersten Test schreiben für ImageStore.checkAndLoadImages(Level). Als erstes benötigen wir ein Image, welches wir als einfaches BufferedImage erzeugen:

Image testImage = new BufferedImage(10, 10, BufferedImage.TYPE_INT_RGB);

Nun benötigen wir ein Mock Objekt für die Klasse ImageService. Und zwar wollen wir, dass beim Aufruf von loadImage(“existing”) unser testImage zurückgegeben wird.

Dies können wir mit folgendem Code sicherstellen:

ImageService imageMockService = Mockito.mock(ImageService.class);
when(imageMockService.loadImage("existing")).thenReturn(testImage);

Die erste Zeile erzeugt das Mock-Objekt und die folgende Zeile gibt bei einem bestimmten Aufruf eine bestimmte Antwort vor.

Hinweis Der when()-Aufruf ist ein Aufruf von Mockito.when(). So wie bei den assert-Aufrufen verwenden wir einen statischen Import, um die Klasse Mockito nicht angeben zu müssen.

Das Level für den Aufruf mocken wir ähnlich, so dass wir dann recht einfach zu unserem Unit-Test kommen können.

@Test
public void loadingImagesOfLevel() {
    Image testImage1 = new BufferedImage(10, 10, BufferedImage.TYPE_INT_RGB);
    Image testImage2 = new BufferedImage(10, 10, BufferedImage.TYPE_INT_RGB);
    ImageService imageMockService = Mockito.mock(ImageService.class);
    when(imageMockService.loadImage("existing1")).thenReturn(testImage1);
    when(imageMockService.loadImage("existing2")).thenReturn(testImage2);

    Level level = Mockito.mock(Level.class);
    when(level.getGraphicResource()).thenReturn("existing1");
    when(level.getChildren()).thenReturn(List.of(GameObject.builder().graphicResource("existing2").build()));

    ImageStore store = new ImageStore(imageMockService);
    store.checkAndLoadImages(level);

    assertAll(
        () -> assertTrue(store.hasImage("existing1")),
        () -> assertTrue(store.hasImage("existing2")),
        () -> assertEquals(testImage1, store.getImage("existing1")),
        () -> assertEquals(testImage2, store.getImage("existing2"))
    );
}

Damit haben wir die Grundlagen, um hier noch weitere Tests zu schreiben.