Contrats axés sur le consommateur avec pacte

Contrats axés sur le consommateur avec pacte

1. Vue d'ensemble

Dans cet article rapide, nous examinerons le concept des contrats axés sur les consommateurs.

Nous allons tester l'intégration avec un service REST externe via un contrat que nous définissons à l'aide de la bibliothèquePact. That contract can be defined by the client, then picked up by the provider and used for development of its services.

Nous créerons également des tests basés sur le contrat pour les applications client et fournisseur.

2. Qu'est-ce quePact?

Using Pact, we can define consumer expectations for a given provider (that can be an HTTP REST service) in the form of a contract (d'où le nom de la bibliothèque).

Nous allons mettre en place ce contrat en utilisant le DSL fourni parPact. Une fois définis, nous pouvons tester les interactions entre les consommateurs et le fournisseur en utilisant le service fictif créé en fonction du contrat défini. De plus, nous testerons le service par rapport au contrat en utilisant un client fictif.

3. Dépendance Maven

Pour commencer, nous devons ajouter une dépendance Maven à la bibliothèque depact-jvm-consumer-junit_2.11:


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

4. Définition d'un contrat

Lorsque nous voulons créer un test en utilisantPact, nous devons d'abord définir un@Rule qui sera utilisé dans notre test:

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

Nous transmettons le nom du fournisseur, l'hôte et le port sur lesquels le serveur simulé (qui est créé à partir du contrat) sera démarré.

Disons que le service a défini le contrat pour deux méthodes HTTP qu'il peut gérer.

La première méthode est une requête GET qui renvoie JSON avec deux champs. Lorsque la demande aboutit, elle renvoie un code de réponse HTTP 200 et l'en-tête Content-Type pour JSON.

Définissons un tel contrat en utilisantPact.

We need to use the @Pact annotation and pass the consumer name for which the contract is defined. À l'intérieur de la méthode annotée, nous pouvons définir notre contrat GET:

@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\"}")
        (...)
}

En utilisant le DSLPact, nous définissons que pour une requête GET donnée, nous voulons renvoyer une réponse 200 avec des en-têtes et un corps spécifiques.

La deuxième partie de notre contrat est la méthode POST. Lorsque le client envoie une requête POST au chemin/pact avec un corps JSON approprié, il renvoie un code de réponse HTTP 201.

Définissons un tel contrat avecPact:

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

Notez que nous devons appeler la méthodetoPact() à la fin du contrat pour renvoyer une instance deRequestResponsePact.

4.1. Artefact du pacte résultant

Par défaut, les fichiers Pact seront générés dans le dossiertarget/pacts. Pour personnaliser ce chemin, nous pouvons configurer lesmaven-surefire-plugin:


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

La build Maven générera un fichier appelétest_consumer-test_provider.json dans le dossiertarget/mypacts qui contient la structure des requêtes et des réponses:

{
    "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. Test du client et du fournisseur à l'aide du contrat

Maintenant que nous avons notre contrat, nous pouvons créer des tests pour le client et le fournisseur.

Chacun de ces tests utilisera un modèle de contrepartie basé sur le contrat, à savoir:

  • le client utilisera un fournisseur factice

  • le fournisseur utilisera un client fictif

En effet, les tests sont effectués par rapport au contrat.

5.1. Tester le client

Une fois le contrat défini, nous pouvons tester les interactions avec le service qui sera créé sur la base de ce contrat. We can create normal JUnit test but we need to remember to put the @PactVerification annotation at the beginning of the test.

Écrivons un test pour la requête GET:

@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");
}

The @PactVerification annotation takes care of starting the HTTP service. Dans le test, il nous suffit d'envoyer la requête GET et d'affirmer que notre réponse est conforme au contrat.

Ajoutons également le test pour l'appel de la méthode POST:

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);

Comme nous pouvons le voir, le code de réponse pour la requête POST est égal à 201 - exactement comme il a été défini dans le contratPact.

Comme nous utilisions l'annotation@PactVerification(), la bibliothèquePact démarre le serveur Web en fonction du contrat précédemment défini avant notre cas de test.

5.2. Tester le fournisseur

La deuxième étape de notre vérification du contrat consiste à créer un test pour le fournisseur à l'aide d'un client fictif basé sur le contrat.

La mise en œuvre de notre fournisseur sera régie par ce contrat en mode TDD.

Pour notre exemple, nous utiliserons une API REST Spring Boot.

Tout d'abord, pour créer notre test JUnit, nous devons ajouter la dépendancepact-jvm-provider-junit_2.11:


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

Cela nous permet de créer un test JUnit en utilisant lesPactRunner et en spécifiant le nom du fournisseur et l'emplacement de l'artefact Pact:

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

Pour que cette configuration fonctionne, nous devons placer le fichiertest_consumer-test_provider.json dans le dossierpacts de notre projet de service REST.

Ensuite, nous définirons la cible à utiliser pour vérifier les interactions dans le contrat et démarrerons l'application Spring Boot avant d'exécuter les 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);
}

Enfin, nous préciserons les états du contrat que nous souhaitons tester:

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

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

L'exécution de cette classe JUnit exécutera deux tests pour les deux requêtes GET et POST. Jetons un œil au journal:

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)

Notez que nous n'avons pas inclus le code de création d'un service REST ici. Le service complet et le test peuvent être trouvés dans lesGitHub project.

6. Conclusion

Dans ce rapide tutoriel, nous avons examiné les contrats axés sur le consommateur.

Nous avons créé un contrat en utilisant la bibliothèquePact. Une fois que nous avons défini le contrat, nous avons pu tester le client et le service par rapport au contrat et affirmer qu'ils étaient conformes à la spécification.

L'implémentation de tous ces exemples et extraits de code peut être trouvée dans leGitHub project - il s'agit d'un projet Maven, il devrait donc être facile à importer et à exécuter tel quel.