Consumentgerichte contracten met pact

1. Overzicht

In dit korte artikel zullen we kijken naar het concept van consumentgerichte contracten.

We testen de integratie met een externe REST-service via een contract dat we definiëren met behulp van de Pact bibliotheek. Dat contract kan door de klant worden gedefinieerd, vervolgens worden opgepikt door de provider en worden gebruikt voor de ontwikkeling van zijn diensten.

We maken ook tests op basis van het contract voor zowel de client- als de providertoepassing.

2. Wat is Pact?

Gebruik makend van Pact, we kunnen de verwachtingen van de consument voor een bepaalde provider (dat kan een HTTP REST-service zijn) definiëren in de vorm van een contract (vandaar de naam van de bibliotheek).

We gaan dit contract opzetten met behulp van de DSL van Pact. Eenmaal gedefinieerd, kunnen we interacties tussen consumenten en de provider testen met behulp van de mock-service die is gemaakt op basis van het gedefinieerde contract. We zullen de service ook testen aan de hand van het contract door een nepcliënt te gebruiken.

3. Maven Afhankelijkheid

Om aan de slag te gaan, moeten we Maven-afhankelijkheid toevoegen aan pact-jvm-consumer-junit_2.11 bibliotheek:

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

4. Definiëren van een contract

Wanneer we een test willen maken met Pact, moeten we eerst een @Regel die zullen worden gebruikt in onze test:

@Rule publiek PactProviderRuleMk2 mockProvider = nieuw PactProviderRuleMk2 ("test_provider", "localhost", 8080, dit);

We geven de naam, host en poort van de provider door waarop de server-mock (die is gemaakt op basis van het contract) zal worden gestart.

Laten we zeggen dat de service het contract heeft gedefinieerd voor twee HTTP-methoden die het aankan.

De eerste methode is een GET-verzoek dat JSON retourneert met twee velden. Wanneer het verzoek slaagt, retourneert het een 200 HTTP-antwoordcode en de Cinhoud-Type header voor JSON.

Laten we een dergelijk contract definiëren met Pact.

We moeten de @Pact annotatie en geef de consumentennaam door waarvoor het contract is gedefinieerd. Binnen de geannoteerde methode kunnen we ons GET-contract definiëren:

@Pact (consumer = "test_consumer") openbaar 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 ( "{\" voorwaarde \ ": waar, \" naam \ ": \" tom \ "}") (...)}

De ... gebruiken Pact DSL definiëren we dat we voor een bepaald GET-verzoek een antwoord van 200 willen retourneren met specifieke headers en body.

Het tweede deel van ons contract is de POST-methode. Wanneer de client een POST-verzoek naar het pad verzendt / pact met een juiste JSON-body retourneert het een 201 HTTP-antwoordcode.

Laten we een dergelijk contract definiëren met Pact:

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

Merk op dat we het toPact () methode aan het einde van het contract om een ​​exemplaar van RequestResponsePact.

4.1. Resulterend pactartefact

Standaard worden Pact-bestanden gegenereerd in het doelwit / pacten map. Om dit pad aan te passen, kunnen we het maven-surefire-plugin:

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

De Maven-build genereert een bestand met de naam test_consumer-test_provider.json in de target / mypacts map met de structuur van de verzoeken en antwoorden:

{"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 AANVRAAG", ...}], "metadata": {"pact-specificatie": {"version": "3.0.0"}, "pact-jvm": {"version": "3.5.0"}}}

5. Testen van de klant en de aanbieder met behulp van het contract

Nu we ons contract hebben, kunnen we er tests tegen maken voor zowel de klant als de provider.

Elk van deze tests gebruikt een mock van zijn tegenhanger die is gebaseerd op het contract, wat betekent:

  • de klant zal een nepprovider gebruiken
  • de provider zal een nepclient gebruiken

In feite worden de tests uitgevoerd tegen het contract.

5.1. Testen van de klant

Zodra we het contract hebben gedefinieerd, kunnen we de interacties testen met de service die op basis van dat contract wordt gecreëerd. We kunnen een normale JUnit-test maken, maar we moeten niet vergeten de @PactVerification annotatie aan het begin van de test.

Laten we een test schrijven voor het GET-verzoek:

@Test @PactVerification () openbare leegte gegevenGet_whenSendRequest_shouldReturn200WithProperHeaderAndBody () {// when ResponseEntity response = new RestTemplate () .getForEntity (mockProvider.getUrl () + "/ pact", String.class); // dan assertThat (response.getStatusCode (). waarde ()). isEqualTo (200); assertThat (response.getHeaders (). get ("Content-Type"). bevat ("application / json")). isTrue (); assertThat (response.getBody ()). bevat ("voorwaarde", "waar", "naam", "tom"); }

De @PactVerification annotatie zorgt voor het starten van de HTTP-service. In de test hoeven we alleen het GET-verzoek te verzenden en te bevestigen dat ons antwoord in overeenstemming is met het contract.

Laten we ook de test voor de POST-methodeaanroep toevoegen:

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

Zoals we kunnen zien, is de responscode voor het POST-verzoek gelijk aan 201 - precies zoals deze is gedefinieerd in het Pact contract.

Omdat we de @PactVerification () annotatie, de Pact library start de webserver op basis van het eerder gedefinieerde contract voor onze testcase.

5.2. De provider testen

De tweede stap van onze contractverificatie is het maken van een test voor de provider met behulp van een nepklant op basis van het contract.

De implementatie van onze provider zal op TDD-wijze worden aangestuurd door dit contract.

Voor ons voorbeeld gebruiken we een Spring Boot REST API.

Om onze JUnit-test te maken, moeten we eerst de afhankelijkheid pact-jvm-provider-junit_2.11 toevoegen:

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

Hierdoor kunnen we een JUnit-test maken met behulp van de PactRunner en het specificeren van de naam van de provider en de locatie van het pactartefact:

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

Om deze configuratie te laten werken, moeten we de test_consumer-test_provider.json bestand in het pacten folder van ons REST-serviceproject.

Vervolgens definiëren we het doel dat moet worden gebruikt voor het verifiëren van de interacties in het contract en starten we de Spring Boot-app voordat de tests worden uitgevoerd:

@TestTarget openbaar einddoelwit = nieuw HttpTarget ("http", "localhost", 8082, "/ spring-rest"); privé statische ConfigurableWebApplicationContext-applicatie; @BeforeClass openbare statische leegte start () {applicatie = (ConfigurableWebApplicationContext) SpringApplication.run (MainApplication.class); }

Ten slotte specificeren we de staten in het contract die we willen testen:

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

Als u deze JUnit-klasse uitvoert, worden twee tests uitgevoerd voor de twee GET- en POST-verzoeken. Laten we het logboek eens bekijken:

Verificatie van een pact tussen test_consumer en test_provider Gegeven test GET GET REQUEST retourneert een antwoord met statuscode 200 (OK) inclusief kopteksten "Content-Type" met waarde "application / json" (OK) heeft een overeenkomende body (OK) Een pact verifiëren tussen test_consumer en test_provider Gegeven test POST POST REQUEST retourneert een antwoord met statuscode 201 (OK) heeft een overeenkomende body (OK)

Merk op dat we de code voor het maken van een REST-service hier niet hebben opgenomen. De volledige service en test zijn te vinden in het GitHub-project.

6. Conclusie

In deze korte tutorial hebben we een kijkje genomen bij Consumer Driven Contracts.

We hebben een contract gemaakt met de Pact bibliotheek. Nadat we het contract hadden gedefinieerd, waren we in staat om de klant en service aan het contract te toetsen en te bevestigen dat ze voldoen aan de specificatie.

De implementatie van al deze voorbeelden en codefragmenten is te vinden in het GitHub-project - dit is een Maven-project, dus het moet gemakkelijk te importeren en uit te voeren zijn zoals het is.