Articles

Publié par

Il y a 4 années -

Temps de lecture 8 minutes

WireMock, le Mockito du serveur HTTP

Avec WireMock on peut démarrer un serveur HTTP et le programmer facilement et rapidement. Promis, vous allez voir.

De la ligne de statut, au contenu des réponses en passant par les en-têtes et le délai avant de répondre, nous allons découvrir que l’on peut tout programmer.

On peut donc produire avec WireMock presque tous les cas de tests d’intégration d’une application communiquant avec un serveur HTTP.

Nous allons voir ces possibilités au travers d’un exemple d’une application communiquant avec une autre application par le biais de web services REST.

Montre-moi

Pour montrer les possibilités de WireMock nous allons utiliser l’exemple suivant : soit une application A et un backend B.

Pour simplifier l’exemple nous allons considérer que A et B sont toutes deux des applications qui exposent des web services REST.

Pour construire ses réponses, A interroge B. On se place dans le cas où l’on veut tester A sachant B.

On veut donc pouvoir simuler divers comportements de B. C’est exactement ce que le serveur HTTP programmable de WireMock nous permet.

Silence, ça tourne

Avant de pouvoir démarrer le serveur HTTP, il nous faut tout d’abord importer la librairie.

<dependency>
    <groupId>com.github.tomakehurst</groupId>
    <artifactId>wiremock</artifactId>
    <version>1.45</version>
    <!-- Pas de dépendances transitives pouvant entrer en conflit avec les librairies du projet -->
    <classifier>standalone</classifier>
 <scope>test</test>
</dependency>

Plusieurs possibilités existent pour démarrer WireMock. Nous allons ici utiliser une @Rule JUnit, fournie par WireMock, qui permet de démarrer et arrêter le serveur HTTP respectivement avant et après chaque test.

@Rule
public WireMockRule wireMockRule = new WireMockRule(8090);

NB : Le démarrage et l’arrêt du serveur est rapide, on ne se prive donc pas de le faire pour chaque test. Au besoin, on pourra affiner et le faire pour la classe ou une suite de tests via les mécanismes fournis par JUnit.

Tout est là on a un serveur HTTP qui tourne sur le port 8090 et qui est prêt à obéir aux ordres. Il ne nous reste plus qu’à le programmer.

Fais ce que je dis, pas ce que je fais

Poursuivons avec notre exemple : application A à tester et backend B à simuler.

Dans un premier temps nous découvrirons comment programmer des cas nominaux. Nous programmerons ensuite des cas d’erreurs : réponses erronées, timeouts. Et enfin nous effectuerons des vérifications auprès du serveur.

Réponds moi

Faire répondre le serveur HTTP que l’on vient de démarrer est très simple. On peut le faire par exemple par le biais d’un fichier de configuration JSON qui, par convention, est placé sous src/test/resources/mappings.

{
    "request": {
        "method": "GET",
        "url": "/serviceB"
    },
    "response": {
        "status": 200,
        "headers": {
            "Content-Type": "application/xml"
        },
        "body": "La réponse B"
    }
}

Le serveur, vous l’aurez compris, répondra pour les requêtes GET sur "/serviceB" avec un statut HTTP 200, un en-tête de contenu "application/xml" et un corps de réponse égal à "La réponse B".

Notre test d’intégration du service A ressemblera donc à peu de choses près à ceci.

public class ServiceA_IT {
 
 @Rule
 public WireMockRule wireMockRule = new WireMockRule(8090);

 WebTarget webTarget = ClientBuilder.newClient().target("http://localhost:8080/serviceA");
 
 @Test
 public void test_service_a_works_well_when_b_answers_correctly() {

  // La réponse de B est configurée par le fichier JSON
 
  String reponseA = webTarget.request().get(String.class);
 
  Assertions.assertThat(reponseA).isEqualTo("A dit que B a répondu : La réponse B");
 }
}

Sympa, mais on peut aller plus loin.

Tu dis n’importe quoi

On peut en effet aller bien au delà et tester notamment que A renvoie, par exemple, un message d’erreur donné lorsque B répond en erreur. Voyons cette fois-ci comment programmer le serveur HTTP directement dans notre test Java.

import static com.github.tomakehurst.wiremock.client.WireMock.*;
 
public class ServiceA_IT {
 
 @Rule
 public WireMockRule wireMockRule = new WireMockRule(8090);

 WebTarget webTarget = ClientBuilder.newClient().target("http://localhost:8080/serviceA");
 
 @Test
 public void test_service_a_works_well_when_b_says_nothing_interesting() {
 
  stubFor(get(urlEqualTo("/serviceB")).willReturn(aResponse().withStatus(500)));
  
  String reponseA = webTarget.request().get(String.class);
  
  Assertions.assertThat(reponseA).isEqualTo("A dit que B a répondu n'importe quoi");
 }
}

On peut, à l’aide de WireMock, programmer le serveur pour tout type de requête : dans l’exemple ci-dessus le serveur répond avec un statut HTTP 500 pour les requêtes GET sur « /serviceB ».

On peut également faire répondre le serveur de manière adaptée en fonction des patterns d’URL, des en-têtes transmis ou même du contenu des requêtes pour du POST par exemple.

get(urlMatching("/serviceB/[0-9]+")).willReturn(...);

get(...).withHeader("Accept", matching("text/.*")).willReturn(...);

post(...).withRequestBody(matching("<status>OK</status>")).willReturn(...); 

Tu ne réponds jamais

Une fonctionnalité importante qu’apporte WireMock est la possibilité de fixer des délais de réponse du serveur HTTP de manière très simple. Ceci nous permet de tester que A est robuste à des temps de réponse de B trop importants par exemple.

import static com.github.tomakehurst.wiremock.client.WireMock.*;
 
public class ServiceA_IT {
 
 @Rule
 public WireMockRule wireMockRule = new WireMockRule(8090);

 WebTarget webTarget = ClientBuilder.newClient().target("http://localhost:8080/serviceA");
 
 @Test
 public void test_service_a_works_well_when_b_is_busy_or_lazy() {
 
  // A est configuré pour ne pas attendre B plus de 1000 ms
  wireMockRule.addRequestProcessingDelay(1100); // On configure le serveur pour répondre après 1100 ms
  
  String reponseA = webTarget.request().get(String.class);
  
  Assertions.assertThat(reponseA).isEqualTo("A dit que B n'a pas répondu dans les temps");
 }
}

Dans le cas ci-dessus on programme le serveur HTTP pour que toutes les requêtes ne soient pas traitées avant un délai de 1100 ms. Ce délai de réponse peut également être positionné pour un ensemble précis de requêtes.

get(urlEqualTo("/serviceB")).willReturn(aResponse().withFixedDelay(1100));

Je ne te parle plus

Nous avons vu jusqu’à présent que WireMock permet de programmer des réponses sur le serveur HTTP. Cette possibilité est similaire à une fonctionnalité principale de Mockito qui permet de programmer les réponses de méthodes d’un objet mock. Une autre fonctionnalité principale de Mockito est de pouvoir effectuer des vérifications des méthodes appelées sur un objet mock. WireMock possède une fonction équivalente qui permet d’effectuer des vérifications des requêtes reçues par le serveur HTTP. Nous allons utiliser cette fonctionnalité pour tester que le service A met bien en cache les réponses du service B.

import static com.github.tomakehurst.wiremock.client.WireMock.*;
 
public class ServiceA_IT {

 @Rule
 public WireMockRule wireMockRule = new WireMockRule(8090);

 WebTarget webTarget = ClientBuilder.newClient().target("http://localhost:8080/serviceA");

 @Test
 public void test_service_a_sets_b_response_in_cache() {
 
  // La réponse de B est configurée par le fichier JSON
  
  String reponseA_1 = webTarget.request().get(String.class); // Requête 1 : A doit interroger B et mettre en cache la réponse
  String reponseA_2 = webTarget.request().get(String.class); // Requête 2 : A doit servir la réponse en cache sans interroger B à nouveau
  
  Assertions.assertThat(reponseA_1).isEqualTo("A dit que B a répondu : La réponse B");
  Assertions.assertThat(reponseA_2).isEqualTo("A dit que B a répondu : La réponse B");
  verify(1, getRequestedFor(urlEqualTo("/serviceB"))); // Vérifier que B n'a été interrogé qu'une seule fois
 }
}

Le test consiste à vérifier auprès du serveur HTTP de WireMock que l’URL « /serviceB » n’a été interrogée qu’une seule fois. On peut ainsi facilement vérifier que A met bien en cache le résultat de l’appel à B.

Au final

A travers ces exemples, nous avons vu que WireMock permet de simuler simplement un large spectre de comportements d’une application exposant une interface HTTP (réponses attendues, réponses en erreur, timeouts). Nous avons également vu qu’à l’instar de Mockito pour les tests unitaires, WireMock permet d’effectuer des vérifications sur les requêtes HTTP reçues par le serveur. Ceci offre la possibilité de tester encore plus de cas comme nous l’avons vu avec l’exemple du test de mise en cache.

On pourrait imaginer utiliser un serveur NodeJS pour simuler des réponses HTTP mais l’avantage de WireMock est qu’il s’exécute avec JUnit et ne lie donc pas le build de l’application à un programme présent sur la machine.WireMock est donc un outil de premier choix pour automatiser des tests d’intégration d’une application communiquant avec un serveur HTTP.

Au delà des tests d’intégration automatisés, on peut également imaginer une utilisation de WireMock pour des tests de performance. On peut en effet isoler les temps de réponse de l’application que l’on mesure, des temps de réponse des systèmes sous-jacents – le temps de réponse de WireMock peut être considéré comme fixe ou même négligeable. WireMock convient donc parfaitement à des mesures de performances en continu par exemple.

Référence

Le site de la librairie : http://wiremock.org/index.html

Publié par

Publié par Sergio Dos Santos

Craft / DevOps / Back / Front / Cloud

Commentaire

1 réponses pour " WireMock, le Mockito du serveur HTTP "

  1. Publié par , Il y a 4 années

    Bonjour,

    Merci pour cet article qui pointe un outil vraiment pratique.

    A noter que ce dernier peut être utilisé hors des tests d’intégration, en mode « standalone ». La configuration se trouve alors au sein du dossier « mappings » et peut servir des fichiers présents au sein du dossier « __files ».

    Plus d’informations ici: http://wiremock.org/getting-started.html#running-standalone

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Nous recrutons

Être un Xebian, c'est faire partie d'un groupe de passionnés ; C'est l'opportunité de travailler et de partager avec des pairs parmi les plus talentueux.