Pact.
Faisons donc un zoom sur ces tests :
Côté fournisseur nous aurons besoin de deux choses :
La baseClassForTests
est une classe mère qui sera utilisée par tous les tests générés, nous y reviendrons.
La première étape vise donc à créer un contrat (au format yaml ou avec un DSL groovy), dans lequel on décrit la requête et la réponse. L’exemple suivant est simpliste, il est possible de faire des choses bien plus poussées (notamment avec des expressions régulières ou autres, je vous invite à lire la doc). On stockera ce contrat côté fournisseur dans le répertoire src/test/resources/contracts, avec le corps de la réponse, également dans un fichier :
À ce moment si vous exécutez Maven, le mécanisme va se mettre en place… ou du moins en partie. La classe de test va être générée (dans target/generated-test-sources/contracts) :
Mais le test va échouer…
En effet, nous avons besoin de la classe de base dont nous parlions plus haut pour initialiser un contexte et faire en sorte que le test passe :
Pas mal de choses à ce niveau :
À ce moment-là, les tests côté fournisseur devraient passer, et si vous avez lancé un mvn install ou deploy, le jar contenant les stubs devrait être publié dans le repo Maven (local ou distant) :
Installing connection-lookup/target/connection-lookup-0.0.1-SNAPSHOT-stubs.jar to ~/.m2/repository/ch/octo/blog/connection-lookup/0.0.1-SNAPSHOT/connection-lookup-0.0.1-SNAPSHOT-stubs.jar
Passons à présent côté consommateur (notre module journey-booking).
Nous avons ici besoin de la dépendance permettant d’utiliser le stub généré côté fournisseur :
Par ailleurs, il nous faut remplacer la dépendance wiremock avec celle de Spring Cloud sous peine d’avoir une exception. Si vous vous souvenez de notre test du client, nous utilisions wiremock pour simuler un serveur. Spring Cloud fait de même mais se base sur le stub auto-généré, ce qui permet d’être aligné avec le contrat du fournisseur.
Le code est le suivant (lien gitlab) :
Le test est relativement proche de notre test de composant (ConnectionLookupClientCompTest), à ceci près que nous ne manipulons plus wiremock directement. C’est l’annotation @AutoConfigureStubRunner
qui va configurer le stub, en allant chercher la dépendance Maven spécifiée par l’attribut ids (au format groupId:artifactId:version:classifier
) et en l’exposant sur le port 8090.
En utilisant le + comme numéro de version, on s’assure de prendre la plus récente, ce qui permet de vérifier que notre test passe toujours malgré les évolutions du fournisseur. Le jour où le test ne passe plus, nous savons que le contrat a changé, logiquement avant que le fournisseur ait mis en production la nouvelle version...
Comme nous venons de le voir, le test de contrat n’est autre qu’un test de composant, mais il a en plus l’avantage de valider que fournisseur et consommateur(s) sont toujours alignés. Je ne saurais donc que recommander d’utiliser ce dernier, en tout cas dans un environnement maîtrisé (typiquement des microservices au sein de votre entreprise). Cela n’a pas de sens lorsqu’on utilise une open API (typiquement l’API de transport), dans ce cas, on restera sur un test de composant.
Dans les 2 cas, je considère ces tests suffisamment importants et relativement rapides à exécuter pour être intégrés au build continu, encore et toujours dans l’objectif d’avoir une feedback rapide.
Les tests de contrats, souvent associés au pattern Consumer-Driven Contracts, sont donc un excellent moyen de vérifier que consommateur et fournisseur d’un service (qu’il soit REST ou via un message) sont toujours alignés sur un contrat commun et partagé. Ils ont également l’avantage de s’exécuter assez rapidement (isolation grâce à wiremock) et donc intégrables à la chaîne d’intégration continue. Dans le prochain article, nous aborderons des tests beaucoup moins simples à exécuter puisqu’il s’agit des tests d’intégration et tests de bout en bout.