API Testing mit Java und WireMock

API Testing mit Java und WireMock

Motivation

Ein bekanntes Problem: Öffentliche APIs werden abgefragt und liefern Daten in Form von JSON oder anderen Formaten zurück. Diese Daten können sich je nach Abfrageort, Zeit oder Umstände ändern. Ein Programm nimmt diese Daten entgegen und verarbeitet sie weiter. Doch wie lässt sich ein automatisierter Test gestalten, wenn sich die Eingangsdaten ständig ändern können? Wie kann ein Algorithmus zuverlässig und automatisiert getestet werden?
Um dieses Problem zu lösen, wird das Konzept des Mockens eingesetzt. Dabei wird nicht mit der original API gearbeitet, sondern ein so genannter Mock, also ein simuliertes Objekt, verwendet. Dieser Blogeintrag zeigt das Konzept mit WireMock und verwendet als Demo die Open Weather Map API.

Benötigte Komponenten

  1. OpenWeather API Key
  2. WireMock
  3. Java

WireMock einbinden

Über Maven lässt sich WireMock sehr komfortabel einbinden, einfach nachfolgende Dependencies im Projekt ergänzen.

<dependency>
 <groupId>com.github.tomakehurst</groupId>
 <artifactId>wiremock</artifactId>
 <version>1.58</version>
 <scope>test</scope>
</dependency>
<dependency>
 <groupId>com.fasterxml.jackson.core</groupId>
 <artifactId>jackson-databind</artifactId>
 <version>2.12.4</version>
</dependency>
<dependency>
 <groupId>com.fasterxml.jackson.core</groupId>
 <artifactId>jackson-core</artifactId>
 <version>2.12.4</version>
</dependency>
<dependency>
 <groupId>com.fasterxml.jackson.core</groupId>
 <artifactId>jackson-annotations</artifactId>
 <version>2.12.4</version>
</dependency>

WireMock und Junit

WireMock simuliert eine HTTP (oder HTTPS) basierte API. Dies ermöglicht es auf APIs zuzugreifen die nicht immer verfügbar sind oder bei der ein Zugriff teuer sein kann. Sogar wenn die API noch nicht fertig entwickelt ist, lassen sich Tests schreiben und somit wertvolle Zeit einsparen. WireMock startet einen Server, lädt dort das vorgegebene API-Abfrageergebnis und ermöglicht einen Zugriff darauf.
WireMock arbeitet hervorragend mit JUnit zusammen. Über die Rule-Annotation lässt sich der Lifecycle des Servers kompakt und einfach steuern. Kein Server Starten und Beenden ist mehr durch den Entwickler nötig.

@Rule
public WireMockRule wireMockRule = new WireMockRule(wireMockConfig().dynamicPort());

Durch die Rule-Annotation kümmert sich JUnit ganz automatisch um den WireMock Server. Über die wireMockConfig lassen sich noch verschiedene Konfigurationen mitgeben. In diesem Beispiel wird immer ein zufälliger, freier Port für den Server verwendet.

Erstes Beispiel

Nachfolgend wird der erste Mock erstellt.

wireMockRule.stubFor(get(WireMock.urlPathEqualTo("/data/2.5/weather")).willReturn(aResponse().withStatus(200)
            .withHeader("Content-Type", "application/json; charset=utf-8").withBody("hier steht der Body")));

In der Methode "urlPathEqualTo" wird hinterlegt auf welche URL reagiert werden soll. Sobald also "/data/2.5/weather" vom abfragenden Programm aufgerufen wird, erfolgt eine Umleitung an den WireMock Server und wird nicht zur API durchgestellt.
In der "willReturn" Methode wird hinterlegt, was genau passieren soll, wenn besagte URL aufgerufen wird. Es erfolgt eine Antwort mit dem Status 200 und sowohl einem Header als auch einem Body.
Im Anschluss an diese Rule lässt sich dann der Test aufrufen und das Ergebnis vergleichen.
Beispiel:

@Test
public void howIsTheWetherTodayDefaultCity() throws IOException {
    final String JSON_FILE_PATH = "weatherskill/cloudy_frankfurt.json";
    final int PORT = wireMockRule.port();
    final WeatherSkill weatherskill = new WeatherSkill(HOST + ":" + PORT);
    wireMockRule.stubFor(get(WireMock.urlPathEqualTo("/data/2.5/weather")).withQueryParam("q", WireMock.equalTo(
        "Frankfurt+am+Main")).withQueryParam("appid", WireMock.notMatching("")).willReturn(aResponse().withStatus(200)
            .withHeader("Content-Type", "application/json; charset=utf-8").withBodyFile(JSON_FILE_PATH)));
    String response = weatherskill.handle("wie wird das wetter heute");
    assertEquals("Heute ist Mäßig bewölkt angesagt in Frankfurt am Main", response);
}

Der WeatherSkill greift auf die Open Weather API zurück und wertet anhand von bestimmten Phrasen das Wetter aus. Wie eingangs erwähnt, ist das Wetter immer verschieden und somit könnte der Test heute erfolgreich sein, aber morgen schon wieder fehlschlagen, nur weil die API ein anderes Ergebnis liefert.
Anstatt wie oben einfach nur einen Body zurückzuliefern, antwortet WireMock hier mit einer Datei, daher auch die "withBodyFile"-Methode.

Mit Dateien antworten

Für die Lesbarkeit und Analyse ist es deutlich einfacher, Dateien auszulagern und nicht direkt im Quellcode zu pflegen. WireMock unterstützt dies, in dem es standardmäßig auf "srctestresources__files"- Verzeichnis zugreift.
Beispielhaft sei hier ein Auszug aus einer der Open Weather API Antworten aufgezeigt.

All diese Daten würden den Testcode nur unübersichtlich gestalten und ließen sich auch schwierig austauschen. Insbesondere dann, wenn viele verschiedene solcher Ergebnisse zum Testen verwendet werden.

Fazit

WireMock ist ein sehr gut geeignetes Tool für die Testautomatisierung von APIs. Gerade wenn die zurückgelieferten API-Ergebnisse sich stetig ändern und dies ein ideales Werkzeug, um das Testen zu vereinfachen. Es spart besonders Zeit, wenn APIs sich noch in der Entwicklung befinden. Die Bedienung ist intuitiv und leicht erlernbar.
Für produktiven Testcode, bei dem WireMock zum Einsatz kam, empfehle ich mein Github Repository, speziell, das von meinem Voiceassistant.

Interne Schulungen in Cucumber & Selenium


In geschützter Umgebung lernt und festigt Ihr Team individuelles Wissen für Ihre aktuellen Anforderungen. Damit Ihr Projekt erfolgreich und wie geplant abgeschlossen wird.

Das könnte Sie auch interessieren

ReSpeaker 4 LEDs mit Pi4J steuern

ReSpeaker 4 LEDs mit Pi4J steuern Motivation Für ReSpeaker im allgemeinen gibt es bereits zahlreiche Bibliotheken, auch einige die LEDs über die GPIO Pins ansteuern....

Licht steuern mit Java und Philips Hue

Licht steuern mit Java und Philips Hue Motivation Für meinen selbstprogrammierten Sprachassistenten wollte ich ein Modul für die Lichtsteuerung entwickeln. Ziel war es,...

Sealed Classes mit Java JDK 17

Sealed Classes mit Java JDK 17 Was sind Sealed Classes? Mit dem kommenden Java Release 17 werden sogenannte Sealed Classes eingeführt. Diese waren zuvor bereits seit...

Behaviour Driven Development (BDD) mit Cucumber und Gherkin Schulung

Behaviour Driven Development (BDD) mit Cucumber Schulung Schulungsbeschreibung In dieser Schulung lernen Teilnehmer*innen den sicheren Umgang mit BDD, Cucumber und...

Rolladensteuerung mit Loxone und Java

Automatische Rolladensteuerung mit Java und Loxone Motivation Loxone ist ein bekannter Anbieter für Smarthome Rolläden. Während sich über die mitgelieferte Oberfläche...

Unit Tests mit AI – Machinet Testfallerstellung

Unit Tests mit AI - Machinet Testfallerstellung Werden Tester bald durch KIs ersetzt? Spoiler: Nein In diesem Blogbeitrag möchte ich auf ein paar Grundlagen der KI...

Erinnerungsassistent für Medikamente mit Sprachausgabe

Erinnerungsassistent für Medikamenteinnahme mit Sprachausgabe Motivation Bevor Corona began, besuchten Ich und meine Familie meine Großeltern regelmäßig. Dabei...

Fünf Tipps für besseren Testcode

Fünf Tipps für besseren Testcode Good Practices In diesem Blogpost zeige ich fünf Good Practices auf, um den eigenen Testcode zu verbessern. Aber wieso nur "Good...

Devops mit Cucumber, Jira und Xray

Devops mit Cucumber, Jira und Xray "Hat auf meiner Maschine funktioniert, ist jetzt Operating Problem“ Diese oder vergleichbare Aussagen sind nicht neu und...