Verbraucherverträge mit Pakt

1. Übersicht

In diesem kurzen Artikel werden wir uns mit dem Konzept verbraucherorientierter Verträge befassen.

Wir werden die Integration mit einem externen REST-Service über einen Vertrag testen, den wir mithilfe der Pact- Bibliothek definieren. Dieser Vertrag kann vom Kunden definiert, dann vom Anbieter abgeholt und für die Entwicklung seiner Dienstleistungen verwendet werden.

Wir erstellen auch vertragliche Tests für die Client- und Provider-Anwendungen.

2. Was ist Pakt ?

Mit Pact können wir die Verbrauchererwartungen für einen bestimmten Anbieter (der ein HTTP-REST-Service sein kann) in Form eines Vertrags (daher der Name der Bibliothek) definieren.

Wir werden diesen Vertrag mit dem von Pact bereitgestellten DSL abschließen . Nach der Definition können wir die Interaktionen zwischen Verbrauchern und Anbietern mithilfe des Mock-Service testen, der auf der Grundlage des definierten Vertrags erstellt wird. Außerdem testen wir den Service anhand des Vertrags mithilfe eines Scheinclients.

3. Maven-Abhängigkeit

Um zu beginnen, müssen wir der Bibliothek pact-jvm-consumer-junit_2.11 die Maven-Abhängigkeit hinzufügen :

 au.com.dius pact-jvm-consumer-junit_2.11 3.5.0 test 

4. Vertrag definieren

Wenn wir einen Test mit Pact erstellen möchten, müssen wir zuerst eine @Rule definieren , die in unserem Test verwendet wird:

@Rule public PactProviderRuleMk2 mockProvider = new PactProviderRuleMk2("test_provider", "localhost", 8080, this);

Wir übergeben den Anbieternamen, den Host und den Port, an dem der Server-Mock (der aus dem Vertrag erstellt wird) gestartet wird.

Angenommen, der Dienst hat den Vertrag für zwei HTTP-Methoden definiert, die er verarbeiten kann.

Die erste Methode ist eine GET-Anforderung, die JSON mit zwei Feldern zurückgibt. Wenn die Anforderung erfolgreich ist, werden ein 200-HTTP-Antwortcode und der C ontent-Type- Header für JSON zurückgegeben.

Definieren wir einen solchen Vertrag mit Pact .

Wir müssen die Annotation @Pact verwenden und den Verbrauchernamen übergeben, für den der Vertrag definiert ist. Innerhalb der mit Anmerkungen versehenen Methode können wir unseren GET-Vertrag definieren:

@Pact(consumer = "test_consumer") public RequestResponsePact createPact(PactDslWithProvider builder) { Map headers = new HashMap(); headers.put("Content-Type", "application/json"); return builder .given("test GET") .uponReceiving("GET REQUEST") .path("/pact") .method("GET") .willRespondWith() .status(200) .headers(headers) .body("{\"condition\": true, \"name\": \"tom\"}") (...) }

Mit dem Pact DSL definieren wir, dass wir für eine bestimmte GET-Anfrage eine 200-Antwort mit bestimmten Headern und Körpern zurückgeben möchten.

Der zweite Teil unseres Vertrages ist die POST-Methode. Wenn der Client eine POST-Anforderung mit einem geeigneten JSON-Body an den Pfad / Pakt sendet , gibt er einen 201 HTTP-Antwortcode zurück.

Definieren wir einen solchen Vertrag mit Pact:

(...) .given("test POST") .uponReceiving("POST REQUEST") .method("POST") .headers(headers) .body("{\"name\": \"Michael\"}") .path("/pact") .willRespondWith() .status(201) .toPact();

Beachten Sie, dass wir die toPact () -Methode am Ende des Vertrags aufrufen müssen , um eine Instanz von RequestResponsePact zurückzugeben .

4.1. Resultierendes Paktartefakt

Standardmäßig werden Paktdateien im Ordner target / pacts generiert . Um diesen Pfad anzupassen, können wir das Maven-Surefire-Plugin konfigurieren :

 org.apache.maven.plugins maven-surefire-plugin   target/mypacts   ... 

Der Maven-Build generiert eine Datei mit dem Namen test_consumer-test_provider.json im Ordner target / mypacts, die die Struktur der Anforderungen und Antworten enthält:

{ "provider": { "name": "test_provider" }, "consumer": { "name": "test_consumer" }, "interactions": [ { "description": "GET REQUEST", "request": { "method": "GET", "path": "/" }, "response": { "status": 200, "headers": { "Content-Type": "application/json" }, "body": { "condition": true, "name": "tom" } }, "providerStates": [ { "name": "test GET" } ] }, { "description": "POST REQUEST", ... } ], "metadata": { "pact-specification": { "version": "3.0.0" }, "pact-jvm": { "version": "3.5.0" } } }

5. Testen des Kunden und des Anbieters anhand des Vertrags

Nachdem wir unseren Vertrag abgeschlossen haben, können wir damit Tests für den Kunden und den Anbieter erstellen.

Bei jedem dieser Tests wird ein Modell des Gegenstücks verwendet, das auf dem Vertrag basiert. Dies bedeutet:

  • Der Client verwendet einen Scheinanbieter
  • Der Anbieter verwendet einen Mock-Client

Tatsächlich werden die Tests gegen den Vertrag durchgeführt.

5.1. Client testen

Sobald wir den Vertrag definiert haben, können wir die Interaktionen mit dem Service testen, der basierend auf diesem Vertrag erstellt wird. Wir können einen normalen JUnit-Test erstellen, müssen jedoch daran denken, die Annotation @PactVerification zu Beginn des Tests einzufügen .

Schreiben wir einen Test für die GET-Anfrage:

@Test @PactVerification() public void givenGet_whenSendRequest_shouldReturn200WithProperHeaderAndBody() { // when ResponseEntity response = new RestTemplate() .getForEntity(mockProvider.getUrl() + "/pact", String.class); // then assertThat(response.getStatusCode().value()).isEqualTo(200); assertThat(response.getHeaders().get("Content-Type").contains("application/json")).isTrue(); assertThat(response.getBody()).contains("condition", "true", "name", "tom"); }

Die Annotation @PactVerification übernimmt das Starten des HTTP-Dienstes. Im Test müssen wir nur die GET-Anfrage senden und bestätigen, dass unsere Antwort dem Vertrag entspricht.

Fügen wir auch den Test für den POST-Methodenaufruf hinzu:

HttpHeaders httpHeaders = new HttpHeaders(); httpHeaders.setContentType(MediaType.APPLICATION_JSON); String jsonBody = "{\"name\": \"Michael\"}"; // when ResponseEntity postResponse = new RestTemplate() .exchange( mockProvider.getUrl() + "/create", HttpMethod.POST, new HttpEntity(jsonBody, httpHeaders), String.class ); //then assertThat(postResponse.getStatusCode().value()).isEqualTo(201);

Wie wir sehen können, ist der Antwortcode für die POST-Anfrage gleich 201 - genau so, wie er im Paktvertrag definiert wurde .

As we were using the @PactVerification() annotation, the Pact library is starting the web server based on the previously defined contract before our test case.

5.2. Testing the Provider

The second step of our contract verification is creating a test for the provider using a mock client based on the contract.

Our provider implementation will be driven by this contract in TDD fashion.

For our example, we'll use a Spring Boot REST API.

First, to create our JUnit test, we'll need to add the pact-jvm-provider-junit_2.11 dependency:

 au.com.dius pact-jvm-provider-junit_2.11 3.5.0 test 

This allows us to create a JUnit test using the PactRunner and specifying the provider name and the location of the Pact artifact:

@RunWith(PactRunner.class) @Provider("test_provider") @PactFolder("pacts") public class PactProviderTest { //... }

For this configuration to work, we have to place the test_consumer-test_provider.json file in the pacts folder of our REST service project.

Next, we'll define the target to be used for verifying the interactions in the contract and start up the Spring Boot app before running the tests:

@TestTarget public final Target target = new HttpTarget("http", "localhost", 8082, "/spring-rest"); private static ConfigurableWebApplicationContext application; @BeforeClass public static void start() { application = (ConfigurableWebApplicationContext) SpringApplication.run(MainApplication.class); }

Finally, we'll specify the states in the contract that we want to test:

@State("test GET") public void toGetState() { } @State("test POST") public void toPostState() { }

Running this JUnit class will execute two tests for the two GET and POST requests. Let's take a look at the log:

Verifying a pact between test_consumer and test_provider Given test GET GET REQUEST returns a response which has status code 200 (OK) includes headers "Content-Type" with value "application/json" (OK) has a matching body (OK) Verifying a pact between test_consumer and test_provider Given test POST POST REQUEST returns a response which has status code 201 (OK) has a matching body (OK)

Note that we haven't included the code for creating a REST service here. The full service and test can be found in the GitHub project.

6. Conclusion

In this quick tutorial, we had a look at Consumer Driven Contracts.

Wir haben einen Vertrag mit der Paktbibliothek erstellt . Nachdem wir den Vertrag definiert hatten, konnten wir den Kunden und die Dienstleistung anhand des Vertrags testen und bestätigen, dass sie der Spezifikation entsprechen.

Die Implementierung all dieser Beispiele und Codefragmente finden Sie im GitHub-Projekt - dies ist ein Maven-Projekt, daher sollte es einfach zu importieren und auszuführen sein, wie es ist.