Introduction à WireMock

Introduction à WireMock

1. Vue d'ensemble

WireMock est une bibliothèque de stubbing et de simulation de services Web. Il construit un serveur HTTP auquel nous pourrions nous connecter comme nous le ferions avec un service Web réel.

Lorsqu'un serveurWireMock est en action, nous pouvons définir des attentes, appeler le service, puis vérifier ses comportements.

2. Dépendances Maven

Pour pouvoir tirer parti de la bibliothèque WireMock, nous devons inclure la dépendance suivante dans le POM:


    com.github.tomakehurst
    wiremock
    1.58
    test

3. Serveur géré par programme

Cette section couvrira la manière de configurer manuellement un serveur WireMock. i.e. sans le support de la configuration automatique de JUnit. L'utilisation est démontrée par un stub très simple.

3.1. Configuration du serveur

Un serveur WireMock peut être instancié comme ceci:

WireMockServer wireMockServer = new WireMockServer(String host, int port);

Si aucun argument n'est fourni, l'hôte du serveur utilise par défautlocalhost et le port du serveur est8080.

Le serveur peut ensuite être démarré et arrêté en utilisant deux méthodes simples:

wireMockServer.start();

And:

wireMockServer.stop();

3.2. Utilisation de base

La bibliothèque WireMock sera d'abord démontrée par une utilisation de base, où un stub pour une URL exacte sans aucune configuration supplémentaire est fourni. Créons une instance de serveur:

WireMockServer wireMockServer = new WireMockServer();

Le serveur WireMock doit être en cours d'exécution avant que le client ne s'y connecte:

wireMockServer.start();

Le service Web est alors stubé:

configureFor("localhost", 8080);
stubFor(get(urlEqualTo("/example")).willReturn(aResponse().withBody("Welcome to example!")));

Ce tutoriel utilise l’API Apache HttpClient pour représenter un client se connectant au serveur:

CloseableHttpClient httpClient = HttpClients.createDefault();

Une demande est exécutée et une réponse est renvoyée, respectivement, après:

HttpGet request = new HttpGet("http://localhost:8080/example");
HttpResponse httpResponse = httpClient.execute(request);

Nous allons convertir la variablehttpResponse enString en utilisant une méthode d'assistance:

String responseString = convertResponseToString(httpResponse);

Voici l'implémentation de cette méthode d'assistance à la conversion:

private String convertResponseToString(HttpResponse response) throws IOException {
    InputStream responseStream = response.getEntity().getContent();
    Scanner scanner = new Scanner(responseStream, "UTF-8");
    String responseString = scanner.useDelimiter("\\Z").next();
    scanner.close();
    return responseString;
}

Le code suivant vérifie que le serveur a reçu une requête sur l'URL attendue et que la réponse arrivant sur le client correspond exactement à celle qui a été envoyée:

verify(getRequestedFor(urlEqualTo("/example")));
assertEquals("Welcome to example!", stringResponse);

Enfin, le serveur WireMock doit être arrêté pour libérer les ressources système:

wireMockServer.stop();

4. Serveur géré JUnit

Contrairement à la section 3, cette section illustre l'utilisation d'un serveur WireMock à l'aide de JUnitRule.

4.1. Configuration du serveur

Un serveur WireMock peut être intégré aux cas de test JUnit en utilisant l'annotation@Rule. Cela permet à JUnit de gérer le cycle de vie, en démarrant le serveur avant chaque méthode de test et en l'arrêtant après le retour de la méthode.

Semblable au serveur géré par programme, un serveur WireMock géré par JUnit peut être créé en tant qu’objet Java avec le numéro de port indiqué:

@Rule
public WireMockRule wireMockRule = new WireMockRule(int port);

Si aucun argument n'est fourni, le port du serveur prendra la valeur par défaut,8080. L'hôte du serveur, par défautlocalhost, et d'autres configurations peuvent être spécifiées à l'aide de l'interfaceOptions.

4.2. Correspondance d'URL

Après avoir configuré une instanceWireMockRule, l'étape suivante consiste à configurer un stub. Dans cette sous-section, nous allons fournir un stub REST pour un noeud final de service à l'aide d'une expression régulière:

stubFor(get(urlPathMatching("/example/.*"))
  .willReturn(aResponse()
  .withStatus(200)
  .withHeader("Content-Type", "application/json")
  .withBody("\"testing-library\": \"WireMock\"")));

Passons à la création d'un client HTTP, exécutons une requête et recevons une réponse:

CloseableHttpClient httpClient = HttpClients.createDefault();
HttpGet request = new HttpGet("http://localhost:8080/example/wiremock");
HttpResponse httpResponse = httpClient.execute(request);
String stringResponse = convertHttpResponseToString(httpResponse);

L'extrait de code ci-dessus tire parti d'une méthode d'assistance à la conversion:

private String convertHttpResponseToString(HttpResponse httpResponse) throws IOException {
    InputStream inputStream = httpResponse.getEntity().getContent();
    return convertInputStreamToString(inputStream);
}

Cela utilise à son tour une autre méthode privée:

private String convertInputStreamToString(InputStream inputStream) {
    Scanner scanner = new Scanner(inputStream, "UTF-8");
    String string = scanner.useDelimiter("\\Z").next();
    scanner.close();
    return string;
}

Les opérations du stub sont vérifiées par le code de test ci-dessous:

verify(getRequestedFor(urlEqualTo("/example/wiremock")));
assertEquals(200, httpResponse.getStatusLine().getStatusCode());
assertEquals("application/json", httpResponse.getFirstHeader("Content-Type").getValue());
assertEquals("\"testing-library\": \"WireMock\"", stringResponse);

4.3. Demander une correspondance d'en-tête

Nous allons maintenant montrer comment stub une API REST avec la correspondance des en-têtes. Commençons par la configuration du stub:

stubFor(get(urlPathEqualTo("/example/wiremock"))
  .withHeader("Accept", matching("text/.*"))
  .willReturn(aResponse()
  .withStatus(503)
  .withHeader("Content-Type", "text/html")
  .withBody("!!! Service Unavailable !!!")));

Semblable à la sous-section précédente, nous illustrons l'interaction HTTP à l'aide de l'API HttpClient, à l'aide des mêmes méthodes d'assistance:

CloseableHttpClient httpClient = HttpClients.createDefault();
HttpGet request = new HttpGet("http://localhost:8080/example/wiremock");
request.addHeader("Accept", "text/html");
HttpResponse httpResponse = httpClient.execute(request);
String stringResponse = convertHttpResponseToString(httpResponse);

Les vérifications et assertions suivantes confirment les fonctions du stub créé précédemment:

verify(getRequestedFor(urlEqualTo("/example/wiremock")));
assertEquals(503, httpResponse.getStatusLine().getStatusCode());
assertEquals("text/html", httpResponse.getFirstHeader("Content-Type").getValue());
assertEquals("!!! Service Unavailable !!!", stringResponse);

4.4. Demander la correspondance du corps

La bibliothèque WireMock peut également être utilisée pour remplacer une API REST avec une correspondance de corps. Voici la configuration pour un stub de ce type:

stubFor(post(urlEqualTo("/example/wiremock"))
  .withHeader("Content-Type", equalTo("application/json"))
  .withRequestBody(containing("\"testing-library\": \"WireMock\""))
  .withRequestBody(containing("\"creator\": \"Tom Akehurst\""))
  .withRequestBody(containing("\"website\": \"wiremock.org\""))
  .willReturn(aResponse()
  .withStatus(200)));

Maintenant, il est temps de créer un objetStringEntity qui sera utilisé comme corps d'une requête:

InputStream jsonInputStream
  = this.getClass().getClassLoader().getResourceAsStream("wiremock_intro.json");
String jsonString = convertInputStreamToString(jsonInputStream);
StringEntity entity = new StringEntity(jsonString);

Le code ci-dessus utilise l'une des méthodes d'aide à la conversion définies avant,convertInputStreamToString.

Voici le contenu du fichierwiremock_intro.json sur le chemin de classe:

{
    "testing-library": "WireMock",
    "creator": "Tom Akehurst",
    "website": "wiremock.org"
}

Les requêtes et réponses HTTP peuvent être configurées et exécutées comme suit:

CloseableHttpClient httpClient = HttpClients.createDefault();
HttpPost request = new HttpPost("http://localhost:8080/example/wiremock");
request.addHeader("Content-Type", "application/json");
request.setEntity(entity);
HttpResponse response = httpClient.execute(request);

C'est le code de test utilisé pour valider le stub:

verify(postRequestedFor(urlEqualTo("/example/wiremock"))
  .withHeader("Content-Type", equalTo("application/json")));
assertEquals(200, response.getStatusLine().getStatusCode());

4.5. Priorité de stub

Les sous-sections précédentes traitent des situations dans lesquelles une requête HTTP ne correspond qu'à un seul stub. Ce serait plus compliqué s'il y a plus qu'une correspondance pour une demande. Par défaut, le dernier module ajouté sera prioritaire dans un tel cas. Toutefois, les utilisateurs sont autorisés à personnaliser ce comportement pour mieux contrôler les stubs WireMock.

Nous allons démontrer le fonctionnement d'un serveur WireMock lorsqu'une requête à venir correspond à deux stubs différents, avec et sans définition du niveau de priorité, en même temps. Les deux scénarios utiliseront la méthode d'assistance privée suivante:

private HttpResponse generateClientAndReceiveResponseForPriorityTests() throws IOException {
    CloseableHttpClient httpClient = HttpClients.createDefault();
    HttpGet request = new HttpGet("http://localhost:8080/example/wiremock");
    request.addHeader("Accept", "text/xml");
    return httpClient.execute(request);
}

Tout d’abord, configurez deux stubs sans prendre en compte le niveau de priorité:

stubFor(get(urlPathMatching("/example/.*"))
  .willReturn(aResponse()
  .withStatus(200)));
stubFor(get(urlPathEqualTo("/example/wiremock"))
  .withHeader("Accept", matching("text/.*"))
  .willReturn(aResponse()
  .withStatus(503)));

Ensuite, créez un client HTTP et exécutez une requête en utilisant la méthode d'assistance décrite ci-dessus:

HttpResponse httpResponse = generateClientAndReceiveResponseForPriorityTests();

L'extrait de code suivant vérifie que le dernier stub configuré est appliqué, quel que soit celui défini précédemment lorsqu'une demande correspond aux deux:

verify(getRequestedFor(urlEqualTo("/example/wiremock")));
assertEquals(503, httpResponse.getStatusLine().getStatusCode());

Passons aux stubs avec des niveaux de priorité définis, où un nombre inférieur représente une priorité plus élevée:

stubFor(get(urlPathMatching("/example/.*"))
  .atPriority(1)
  .willReturn(aResponse()
  .withStatus(200)));
stubFor(get(urlPathEqualTo("/example/wiremock"))
  .atPriority(2)
  .withHeader("Accept", matching("text/.*"))
  .willReturn(aResponse()
  .withStatus(503)));

Création et exécution d'une requête HTTP:

HttpResponse httpResponse = generateClientAndReceiveResponseForPriorityTests();

Le code suivant valide l'effet des niveaux de priorité, le premier stub configuré étant appliqué à la place du dernier:

verify(getRequestedFor(urlEqualTo("/example/wiremock")));
assertEquals(200, httpResponse.getStatusLine().getStatusCode());

5. Conclusion

Ce tutoriel présente WireMock et explique comment configurer et configurer cette bibliothèque pour le test des API REST à l'aide de diverses techniques, notamment la correspondance des URL, des en-têtes de requête et du corps.

L'implémentation de tous les exemples et extraits de code peut être trouvée dansa GitHub project.