Einführung in WireMock

Einführung in WireMock

1. Überblick

WireMock ist eine Bibliothek zum Stubben und Verspotten von Webdiensten. Es wird ein HTTP-Server erstellt, mit dem wir uns wie mit einem tatsächlichen Webdienst verbinden können.

Wenn einWireMock-Server in Aktion ist, können wir Erwartungen einrichten, den Dienst aufrufen und dann sein Verhalten überprüfen.

2. Maven-Abhängigkeiten

Um die WireMock-Bibliothek nutzen zu können, müssen wir die folgende Abhängigkeit in den POM aufnehmen:


    com.github.tomakehurst
    wiremock
    1.58
    test

3. Programmatisch verwalteter Server

In diesem Abschnitt wird die manuelle Konfiguration eines WireMock-Servers beschrieben. i.e. ohne die Unterstützung von JUnit Auto-Konfiguration. Die Verwendung wird durch einen sehr einfachen Stich demonstriert.

3.1. Servereinrichtung

Ein WireMock-Server kann folgendermaßen instanziiert werden:

WireMockServer wireMockServer = new WireMockServer(String host, int port);

Falls keine Argumente angegeben werden, ist der Serverhost standardmäßiglocalhost und der Serverport8080.

Der Server kann dann mit zwei einfachen Methoden gestartet und gestoppt werden:

wireMockServer.start();

And:

wireMockServer.stop();

3.2. Grundsätzliche Verwendung

Die WireMock-Bibliothek wird zunächst anhand einer grundlegenden Verwendung demonstriert, bei der ein Stub für eine genaue URL ohne weitere Konfiguration bereitgestellt wird. Erstellen wir eine Serverinstanz:

WireMockServer wireMockServer = new WireMockServer();

Der WireMock-Server muss ausgeführt werden, bevor der Client eine Verbindung zu ihm herstellt:

wireMockServer.start();

Der Webservice wird dann gestubbt:

configureFor("localhost", 8080);
stubFor(get(urlEqualTo("/example")).willReturn(aResponse().withBody("Welcome to example!")));

In diesem Lernprogramm wird die Apache HttpClient-API verwendet, um einen Client darzustellen, der eine Verbindung zum Server herstellt:

CloseableHttpClient httpClient = HttpClients.createDefault();

Anschließend wird eine Anfrage ausgeführt bzw. eine Antwort zurückgesendet:

HttpGet request = new HttpGet("http://localhost:8080/example");
HttpResponse httpResponse = httpClient.execute(request);

Wir werden die VariablehttpResponse mit einer Hilfsmethode inString konvertieren:

String responseString = convertResponseToString(httpResponse);

Hier ist die Implementierung dieser Konvertierungshilfemethode:

private String convertResponseToString(HttpResponse response) throws IOException {
    InputStream responseStream = response.getEntity().getContent();
    Scanner scanner = new Scanner(responseStream, "UTF-8");
    String responseString = scanner.useDelimiter("\\Z").next();
    scanner.close();
    return responseString;
}

Mit dem folgenden Code wird überprüft, ob der Server eine Anforderung an die erwartete URL erhalten hat und die beim Client eingehende Antwort genau der gesendeten entspricht:

verify(getRequestedFor(urlEqualTo("/example")));
assertEquals("Welcome to example!", stringResponse);

Schließlich sollte der WireMock-Server gestoppt werden, um Systemressourcen freizugeben:

wireMockServer.stop();

4. JUnit Managed Server

Im Gegensatz zu Abschnitt 3 zeigt dieser Abschnitt die Verwendung eines WireMock-Servers mit Hilfe von JUnitRule.

4.1. Servereinrichtung

Ein WireMock-Server kann mithilfe der Annotation@Rulein JUnit-Testfälle integriert werden. Auf diese Weise kann JUnit den Lebenszyklus verwalten, den Server vor jeder Testmethode starten und nach der Rückkehr der Methode stoppen.

Ähnlich wie beim programmgesteuerten Server kann ein von JUnit verwalteter WireMock-Server als Java-Objekt mit der angegebenen Portnummer erstellt werden:

@Rule
public WireMockRule wireMockRule = new WireMockRule(int port);

Wenn keine Argumente angegeben werden, nimmt der Server-Port den Standardwert8080 an. Der Serverhost mit der Standardeinstellunglocalhost und andere Konfigurationen können über die SchnittstelleOptionsangegeben werden.

4.2. URL-Matching

Nach dem Einrichten einerWireMockRule-Instanz besteht der nächste Schritt darin, einen Stub zu konfigurieren. In diesem Unterabschnitt stellen wir einen REST-Stub für einen Service-Endpunkt mithilfe eines regulären Ausdrucks bereit:

stubFor(get(urlPathMatching("/example/.*"))
  .willReturn(aResponse()
  .withStatus(200)
  .withHeader("Content-Type", "application/json")
  .withBody("\"testing-library\": \"WireMock\"")));

Fahren wir mit dem Erstellen eines HTTP-Clients fort, führen eine Anforderung aus und erhalten eine Antwort:

CloseableHttpClient httpClient = HttpClients.createDefault();
HttpGet request = new HttpGet("http://localhost:8080/example/wiremock");
HttpResponse httpResponse = httpClient.execute(request);
String stringResponse = convertHttpResponseToString(httpResponse);

Das obige Code-Snippet nutzt eine Konvertierungshilfemethode:

private String convertHttpResponseToString(HttpResponse httpResponse) throws IOException {
    InputStream inputStream = httpResponse.getEntity().getContent();
    return convertInputStreamToString(inputStream);
}

Dies macht wiederum von einer anderen privaten Methode Gebrauch:

private String convertInputStreamToString(InputStream inputStream) {
    Scanner scanner = new Scanner(inputStream, "UTF-8");
    String string = scanner.useDelimiter("\\Z").next();
    scanner.close();
    return string;
}

Die Operationen des Stubs werden durch den folgenden Testcode überprüft:

verify(getRequestedFor(urlEqualTo("/example/wiremock")));
assertEquals(200, httpResponse.getStatusLine().getStatusCode());
assertEquals("application/json", httpResponse.getFirstHeader("Content-Type").getValue());
assertEquals("\"testing-library\": \"WireMock\"", stringResponse);

4.3. Header-Matching anfordern

Nun wird gezeigt, wie eine REST-API mit dem Abgleich von Headern erstellt wird. Beginnen wir mit der Stub-Konfiguration:

stubFor(get(urlPathEqualTo("/example/wiremock"))
  .withHeader("Accept", matching("text/.*"))
  .willReturn(aResponse()
  .withStatus(503)
  .withHeader("Content-Type", "text/html")
  .withBody("!!! Service Unavailable !!!")));

Ähnlich wie im vorhergehenden Unterabschnitt veranschaulichen wir die HTTP-Interaktion mithilfe der HttpClient-API mithilfe derselben Hilfsmethoden:

CloseableHttpClient httpClient = HttpClients.createDefault();
HttpGet request = new HttpGet("http://localhost:8080/example/wiremock");
request.addHeader("Accept", "text/html");
HttpResponse httpResponse = httpClient.execute(request);
String stringResponse = convertHttpResponseToString(httpResponse);

Die folgenden Überprüfungen und Zusicherungen bestätigen die Funktionen des zuvor erstellten Stubs:

verify(getRequestedFor(urlEqualTo("/example/wiremock")));
assertEquals(503, httpResponse.getStatusLine().getStatusCode());
assertEquals("text/html", httpResponse.getFirstHeader("Content-Type").getValue());
assertEquals("!!! Service Unavailable !!!", stringResponse);

4.4. Body Matching anfordern

Die WireMock-Bibliothek kann auch verwendet werden, um eine REST-API mit Body-Matching zu erstellen. Hier ist die Konfiguration für einen solchen Stub:

stubFor(post(urlEqualTo("/example/wiremock"))
  .withHeader("Content-Type", equalTo("application/json"))
  .withRequestBody(containing("\"testing-library\": \"WireMock\""))
  .withRequestBody(containing("\"creator\": \"Tom Akehurst\""))
  .withRequestBody(containing("\"website\": \"wiremock.org\""))
  .willReturn(aResponse()
  .withStatus(200)));

Jetzt ist es Zeit, einStringEntity-Objekt zu erstellen, das als Hauptteil einer Anforderung verwendet wird:

InputStream jsonInputStream
  = this.getClass().getClassLoader().getResourceAsStream("wiremock_intro.json");
String jsonString = convertInputStreamToString(jsonInputStream);
StringEntity entity = new StringEntity(jsonString);

Der obige Code verwendet eine der zuvor definierten KonvertierungshilfemethodenconvertInputStreamToString.

Hier ist der Inhalt derwiremock_intro.json-Datei im Klassenpfad:

{
    "testing-library": "WireMock",
    "creator": "Tom Akehurst",
    "website": "wiremock.org"
}

HTTP-Anforderungen und -Antworten können wie folgt konfiguriert und ausgeführt werden:

CloseableHttpClient httpClient = HttpClients.createDefault();
HttpPost request = new HttpPost("http://localhost:8080/example/wiremock");
request.addHeader("Content-Type", "application/json");
request.setEntity(entity);
HttpResponse response = httpClient.execute(request);

Dies ist der Testcode, der zur Validierung des Stubs verwendet wird:

verify(postRequestedFor(urlEqualTo("/example/wiremock"))
  .withHeader("Content-Type", equalTo("application/json")));
assertEquals(200, response.getStatusLine().getStatusCode());

4.5. Stub-Priorität

Die vorherigen Unterabschnitte befassen sich mit Situationen, in denen eine HTTP-Anforderung nur einem einzelnen Stub entspricht. Es wäre komplizierter, wenn es mehr als eine Übereinstimmung für eine Anfrage gibt. Standardmäßig hat in diesem Fall der zuletzt hinzugefügte Stub Vorrang. Benutzer können dieses Verhalten jedoch anpassen, um mehr Kontrolle über WireMock-Stubs zu erhalten.

Wir werden den Betrieb eines WireMock-Servers demonstrieren, wenn eine eingehende Anfrage gleichzeitig zwei verschiedenen Stubs mit und ohne Festlegen der Prioritätsstufe entspricht. In beiden Szenarien wird die folgende private Hilfsmethode verwendet:

private HttpResponse generateClientAndReceiveResponseForPriorityTests() throws IOException {
    CloseableHttpClient httpClient = HttpClients.createDefault();
    HttpGet request = new HttpGet("http://localhost:8080/example/wiremock");
    request.addHeader("Accept", "text/xml");
    return httpClient.execute(request);
}

Konfigurieren Sie zunächst zwei Stubs ohne Berücksichtigung der Prioritätsstufe:

stubFor(get(urlPathMatching("/example/.*"))
  .willReturn(aResponse()
  .withStatus(200)));
stubFor(get(urlPathEqualTo("/example/wiremock"))
  .withHeader("Accept", matching("text/.*"))
  .willReturn(aResponse()
  .withStatus(503)));

Erstellen Sie als Nächstes einen HTTP-Client und führen Sie eine Anforderung mit der oben beschriebenen Hilfsmethode aus:

HttpResponse httpResponse = generateClientAndReceiveResponseForPriorityTests();

Mit dem folgenden Code-Snippet wird überprüft, ob der zuletzt konfigurierte Stub angewendet wird, unabhängig davon, welcher zuvor definiert wurde, wenn eine Anforderung mit beiden übereinstimmt:

verify(getRequestedFor(urlEqualTo("/example/wiremock")));
assertEquals(503, httpResponse.getStatusLine().getStatusCode());

Fahren wir mit Stubs fort, bei denen Prioritätsstufen festgelegt sind, wobei eine niedrigere Zahl eine höhere Priorität darstellt:

stubFor(get(urlPathMatching("/example/.*"))
  .atPriority(1)
  .willReturn(aResponse()
  .withStatus(200)));
stubFor(get(urlPathEqualTo("/example/wiremock"))
  .atPriority(2)
  .withHeader("Accept", matching("text/.*"))
  .willReturn(aResponse()
  .withStatus(503)));

Erstellung und Ausführung einer HTTP-Anfrage:

HttpResponse httpResponse = generateClientAndReceiveResponseForPriorityTests();

Der folgende Code überprüft die Auswirkung der Prioritätsstufen, wobei der erste konfigurierte Stub anstelle des letzten angewendet wird:

verify(getRequestedFor(urlEqualTo("/example/wiremock")));
assertEquals(200, httpResponse.getStatusLine().getStatusCode());

5. Fazit

In diesem Tutorial wurde WireMock vorgestellt, wie diese Bibliothek zum Testen von REST-APIs mithilfe verschiedener Techniken eingerichtet und konfiguriert wird, einschließlich des Abgleichs von URL, Anforderungsheader und Text.

Die Implementierung aller Beispiele und Codefragmente finden Sie ina GitHub project.