In der modernen Softwareentwicklung ist das Testen ein wesentlicher Bestandteil des Entwicklungsprozesses. Tests stellen sicher, dass der Code wie erwartet funktioniert und ermöglichen eine kontinuierliche Integration und Bereitstellung. Ein häufiges Szenario beim Testen ist die Notwendigkeit, externe APIs zu simulieren, um die Abhängigkeiten zu isolieren und wiederholbare Tests durchzuführen. Hier kommt WireMock ins Spiel.

WireMock ist eine flexible und leistungsstarke Java-Bibliothek, die speziell entwickelt wurde, um HTTP-APIs zu simulieren. Mit WireMock können Entwickler Mock-Server erstellen, die HTTP-Anfragen entgegennehmen und vorher definierte Antworten zurückgeben. Dies ermöglicht es, das Verhalten externer Dienste nachzubilden, ohne auf die tatsächlichen Dienste zugreifen zu müssen.

Installation und Einrichtung

Um WireMock in einem Java-Projekt zu verwenden, müssen die entsprechenden Abhängigkeiten in die Projektdatei eingefügt werden. In einem Maven-Projekt fügen wir folgende Dependency in die pom.xml-Datei ein:

<dependency>
    <groupId>com.github.tomakehurst</groupId>
    <artifactId>wiremock-jre8</artifactId>
    <version>2.32.0</version>
    <scope>test</scope>
</dependency>Code-Sprache: HTML, XML (xml)

Für Gradle-Projekte wird die Dependency in die build.gradle-Datei eingefügt:

testImplementation 'com.github.tomakehurst:wiremock-jre8:2.32.0'Code-Sprache: JavaScript (javascript)

Nach dem Hinzufügen der Abhängigkeiten können wir mit der Verwendung von WireMock in unseren Tests beginnen.

Grundlegende Verwendung

Um WireMock in einem Test zu verwenden, müssen wir einen WireMock-Server starten, Anfragen und Antworten konfigurieren und den Server anschließend stoppen. Ein einfacher JUnit-Test mit WireMock könnte wie folgt aussehen:

import com.github.tomakehurst.wiremock.WireMockServer;
import com.github.tomakehurst.wiremock.client.WireMock;
import com.github.tomakehurst.wiremock.core.WireMockConfiguration;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

import static com.github.tomakehurst.wiremock.client.WireMock.aResponse;
import static com.github.tomakehurst.wiremock.client.WireMock.equalTo;
import static com.github.tomakehurst.wiremock.client.WireMock.get;
import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo;
import static org.junit.jupiter.api.Assertions.assertEquals;

public class WireMockTest {

    private WireMockServer wireMockServer;

    @BeforeEach
    public void setup() {
        wireMockServer = new WireMockServer(WireMockConfiguration.options().port(8080));
        wireMockServer.start();
        WireMock.configureFor("localhost", 8080);
    }

    @AfterEach
    public void teardown() {
        wireMockServer.stop();
    }

    @Test
    public void testGetEndpoint() {
        wireMockServer.stubFor(get(urlEqualTo("/api/test"))
            .willReturn(aResponse()
                .withStatus(200)
                .withHeader("Content-Type", "application/json")
                .withBody("{\"message\":\"Hello, World!\"}")));

        // Hier würde der tatsächliche HTTP-Client auf den WireMock-Server zugreifen
        String response = sendHttpRequest("http://localhost:8080/api/test");

        assertEquals("{\"message\":\"Hello, World!\"}", response);
    }

    private String sendHttpRequest(String url) {
        // Methode zum Senden einer HTTP-Anfrage und Rückgabe der Antwort als String
        // Dies könnte beispielsweise mit HttpClient, RestTemplate oder einer anderen HTTP-Client-Bibliothek erfolgen
        return ""; // Dummy-Rückgabe für das Beispiel
    }
}Code-Sprache: JavaScript (javascript)

Erklärung des Codes

  1. Setup und Teardown:
    • In der setup-Methode wird ein WireMock-Server auf Port 8080 gestartet.
    • Die teardown-Methode stoppt den Server nach jedem Test.
    • WireMock.configureFor("localhost", 8080) konfiguriert WireMock, um auf Anfragen an localhost:8080 zu reagieren.
  2. Stubbing von Anfragen:
    • wireMockServer.stubFor(get(urlEqualTo("/api/test")) definiert, dass eine GET-Anfrage an /api/test eine vorgegebene Antwort zurückgibt.
    • willReturn(aResponse().withStatus(200).withHeader("Content-Type", "application/json").withBody("{\"message\":\"Hello, World!\"}")) spezifiziert die Antwort, einschließlich Statuscode, Header und Body.
  3. Senden einer HTTP-Anfrage:
    • Die Methode sendHttpRequest simuliert das Senden einer HTTP-Anfrage. In einem realen Szenario würde dies durch eine HTTP-Client-Bibliothek wie HttpClient oder RestTemplate erfolgen.

Erweiterte Funktionen

WireMock bietet eine Vielzahl von erweiterten Funktionen, die über einfache Stub-Anfragen hinausgehen. Dazu gehören:

Anfragen-Verifizierung

Neben dem Stubbing können wir auch überprüfen, ob bestimmte Anfragen während des Tests gesendet wurden:

@Test
public void testRequestVerification() {
    wireMockServer.stubFor(get(urlEqualTo("/api/verify"))
        .willReturn(aResponse().withStatus(200)));

    sendHttpRequest("http://localhost:8080/api/verify");

    WireMock.verify(WireMock.getRequestedFor(urlEqualTo("/api/verify")));
}Code-Sprache: PHP (php)

Dynamische Antworten

WireMock unterstützt auch dynamische Antworten, die auf den eingehenden Anfragen basieren:

wireMockServer.stubFor(get(urlPathEqualTo("/api/resource"))
    .withQueryParam("id", equalTo("123"))
    .willReturn(aResponse()
        .withStatus(200)
        .withBody("{\"resource\":\"123\"}")));Code-Sprache: PHP (php)

Simulieren von Verzögerungen und Fehlern

Um Netzwerkbedingungen oder Fehler zu simulieren, kann WireMock Verzögerungen und verschiedene Fehlerarten einfügen:

wireMockServer.stubFor(get(urlEqualTo("/api/timeout"))
    .willReturn(aResponse()
        .withFixedDelay(3000) // 3 Sekunden Verzögerung
        .withStatus(200)
        .withBody("Delayed response")));

wireMockServer.stubFor(get(urlEqualTo("/api/error"))
    .willReturn(aResponse()
        .withStatus(500) // Interner Serverfehler
        .withBody("Internal Server Error")));Code-Sprache: PHP (php)

Verwendung in verschiedenen Testframeworks

WireMock lässt sich nahtlos in verschiedene Testframeworks wie JUnit und TestNG integrieren. Hier ist ein Beispiel für die Verwendung mit JUnit 5:

import com.github.tomakehurst.wiremock.junit5.WireMockExtension;
import org.junit.jupiter.api.extension.RegisterExtension;
import org.junit.jupiter.api.Test;

import static com.github.tomakehurst.wiremock.client.WireMock.*;
import static org.junit.jupiter.api.Assertions.assertEquals;

public class WireMockJUnit5Test {

    @RegisterExtension
    static WireMockExtension wireMockExtension = WireMockExtension.newInstance()
        .options(WireMockConfiguration.wireMockConfig().port(8080))
        .build();

    @Test
    public void testWithExtension() {
        stubFor(get(urlEqualTo("/api/extension"))
            .willReturn(aResponse()
                .withStatus(200)
                .withBody("Hello from extension!")));

        String response = sendHttpRequest("http://localhost:8080/api/extension");

        assertEquals("Hello from extension!", response);
    }

    private String sendHttpRequest(String url) {
        // Methode zum Senden einer HTTP-Anfrage und Rückgabe der Antwort als String
        return ""; // Dummy-Rückgabe für das Beispiel
    }
}Code-Sprache: JavaScript (javascript)

Fazit

WireMock ist ein unverzichtbares Werkzeug für Entwickler, die zuverlässige und wiederholbare Tests für ihre Anwendungen erstellen möchten. Durch die Möglichkeit, externe HTTP-Dienste zu simulieren, können Tests isoliert und unabhängig von externen Systemen durchgeführt werden. Dies führt zu stabileren und aussagekräftigeren Testergebnissen.

Mit den vielfältigen Funktionen von WireMock, einschließlich der Möglichkeit, Anfragen zu stubben, zu verifizieren und dynamische Antworten zu erstellen, bietet die Bibliothek eine umfassende Lösung für das Testen von HTTP-Clients und API-Integrationen. Ob in einfachen Unit-Tests oder komplexen Integrationstests, WireMock erleichtert das Testen und trägt zur Qualitätssicherung in der Softwareentwicklung bei.