Go to Hackademy website

Facilitez-vous les tests avec Wiremock !

Benoit Delesalle

Posté par Benoit Delesalle dans les catégories outilsqualité

Il arrive parfois que les applications que nous développons soient dépendantes d’autres applications ou API.

En effet, il n’est pas impossible que mon application ait besoin de communiquer avec des services externes, tel qu’un prestataire de paiement ou une solution d’envoi de mail externalisée par exemple. Lorsque vous développez votre application, vous vous dites que c’est quand même bien de ne pas avoir à réinventer la roue et de pouvoir se baser sur une solution robuste qui a déjà fait ses preuves. Mais lors de l’écriture des tests, vous pouvez rapidement vous retrouver dans cette situation si vous n’avez pas les bons réflexes.

Comment vais-je écrire ce test ?!

Wiremock, l’outil indispensable qui fait l’appli et le beau temps

Heureusement pour vous, je connais un outil qui va grandement vous faciliter l’écriture de vos tests lorsque vous avez ce genre de dépendance, et comme vous l’aurez certainement déjà compris, cet outil c’est Wiremock.

Pour notre exemple, admettons que notre application soit écrite en PHP et qu’elle affiche tout simplement la météo actuelle dans votre ville. On pourrait écrire un service qui se charge de récupérer les informations et de les formater qui ressemblerait à ceci :

<?php

# src/Service/WeatherService.php

namespace App\Service;

use GuzzleHttp\Client;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
use Symfony\Component\HttpFoundation\Request;

class WeatherService
{
    /**
     * @var Client
     */
    private $client;

    /**
     * @var string
     */
    private $baseUrl;

    /**
     * @var ParameterBagInterface
     */
    private $apiKey;

    /**
     * WeatherService constructor.
     *
     * @param ParameterBagInterface $parameters
     */
    public function __construct(ParameterBagInterface $parameters)
    {
        $this->client = new Client();
        $this->apiKey = $parameters->get('weather_api_key');
        $this->baseUrl = $parameters->get('weather_api_base_url');
    }

    /**
     * @param string $city
     * @return array
     */
    public function call(string $city = "Lambersart"): array
    {
        return $this->format(
            $this->getWeather($city)
        );
    }

    /**
     * @param string $city
     * @return array
     */
    private function getWeather(string $city): array
    {
        return json_decode(
            $this->client->request(
                Request::METHOD_GET,
                "$this->baseUrl?units=metric&lang=fr&q=$city&appid=$this->apiKey"
            )
                ->getBody()
                ->getContents(),
            true
        );
    }

    /**
     * @param array $data
     * @return array
     */
    private function format(array $data): array
    {
        return [
            'temperature' => [
                'current' => $data['main']['temp'],
                'feels_like' => $data['main']['feels_like']
            ],
            'description' => array_shift($data['weather'])['description']
        ];
    }
}

On est d’accord qu’on pourrait améliorer le code, mais on a déjà une bonne base pour écrire notre test et qu’il pourrait ressembler à ceci :

<?php

# tests/WeatherServiceTest.php

namespace App\Tests;

use App\Service\WeatherService;
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;

class WeatherServiceTest extends WebTestCase
{
    public function testCall()
    {
        self::bootKernel();

        $weatherService = self::$container->get(WeatherService::class);

        $this->assertEquals(
            [
                'temperature' => [
                    'current' => '7.87',
                    'feels_like' => '0.48'
                ],
                'description' => 'partiellement nuageux'
            ]
            ,
            $weatherService->call('Lille')
        );
    }
}

Si vous vous dites que quelque chose ne va pas dans ce test, alors vous avez tout bon. Effectivement, mon test est dépendant d’une API tierce et on ne peut garantir qu’à un instant T, elle renverra la même chose qu’à l’instant d’avant et on devrait mocker le résultat de cet appel car dans le fond ce qui nous intéresse réellement c’est de savoir si notre service fait bien ce qu’il est censé faire. Ainsi, l’objet de notre test est de s’assurer que le bon appel est fait à l’API, non pas de vérifier le retour de cet appel.

Pour cela, rien de plus simple avec Wiremock. En effet, il suffit d’avoir un serveur Wiremock qui tourne et qui va servir de proxy vers l’API de météo. Ici, nous allons le lancer dans un container docker pour plus de facilité.

docker run -d -p 81:8080 ekino/wiremock --proxy-all="http://api.openweathermap.org/data/2.5/weather" --record-mappings

Ici, un serveur Wiremock est lancé et disponible sur http://localhost:81. Si on accède à l’administration via http://localhost:81/__admin/, on peut voir qu’actuellement nous n’avons aucun mapping, à comprendre aucun mock enregistré. Dans notre environnement de test, la base_url de l’API météo doit désormais pointer sur notre serveur Wiremock http://localhost:81/.

Voici un exemple de configuration pour Symfony.

# .env

WEATHER_API_BASE_URL=http://api.openweathermap.org/data/2.5/weather
# .env.test

WEATHER_API_BASE_URL=http://localhost:81

Une fois ceci fait, enregistrons notre premier mock. Pour ce faire, rendons-nous sur http://localhost:81/__admin/recorder/, renseignons l’URL de notre API de météo, à savoir http://api.openweathermap.org/data/2.5/weather, cliquons sur Record, relançons notre test, et enfin cliquons sur Stop. Nous venons d’enregistrer notre premier mock qui est désormais visible sur la page d’administration.

{
   "mappings":[
      {
         "id":"48a59cf8-c8ec-45d4-8b00-f8d3952d4cfc",
         "name":"",
         "request":{
            "url":"/?units=metric&lang=fr&q=Lambersart&appid=088aa94d9b354402acf23fcaba0ac4f6",
            "method":"GET"
         },
         "response":{
            "status":200,
            "body":"{\"coord\":{\"lon\":3.03,\"lat\":50.65},\"weather\":[{\"id\":800,\"main\":\"Clear\",\"description\":\"ciel dégagé\",\"icon\":\"01d\"}],\"base\":\"stations\",\"main\":{\"temp\":16.3,\"feels_like\":10.34,\"temp_min\":14.44,\"temp_max\":17.78,\"pressure\":1026,\"humidity\":39},\"visibility\":10000,\"wind\":{\"speed\":6.2,\"deg\":250},\"clouds\":{\"all\":0},\"dt\":1584543344,\"sys\":{\"type\":1,\"id\":6559,\"country\":\"FR\",\"sunrise\":1584510863,\"sunset\":1584554240},\"timezone\":3600,\"id\":3008218,\"name\":\"Lambersart\",\"cod\":200}",
            "headers":{
               "Server":"openresty",
               "Date":"Wed, 18 Mar 2020 15:00:18 GMT",
               "Content-Type":"application/json; charset=utf-8",
               "Connection":"keep-alive",
               "X-Cache-Key":"/data/2.5/weather/?lang=fr&q=lambersart&units=metric",
               "Access-Control-Allow-Origin":"*",
               "Access-Control-Allow-Credentials":"true",
               "Access-Control-Allow-Methods":"GET, POST"
            }
         },
         "uuid":"48a59cf8-c8ec-45d4-8b00-f8d3952d4cfc",
         "persistent":true
      }
   ],
   "meta":{
      "total":1
   }
}

Adaptons notre test avec les valeurs retournées dans ce mock dans la partie response.

Voilà, à partir de maintenant, en environnement de test, notre application n’appellera plus l’API météo mais prendra comme acquis les valeurs de notre mock pour cet appel HTTP.

Ainsi se termine notre article concernant Wiremock et avouez-le, vous êtes bien content de ne plus devoir écrire vos mocks à la main.

Ressources


L’équipe Synbioz.
Libres d’être ensemble.

Articles connexes

Les e-mails c'est bien, mangez-en !

13/02/2020

Les mails, c’est compliqué.

Una : pourquoi avoir créé notre propre ERP ?

13/01/2020

Una, qu’est-ce que c’est ? Una — prononcez [‘una] — est un progiciel de gestion intégré (PGI ou en anglais ERP pour « Enterprise Resource Planning ») développé par Synbioz pour ses propres besoins....

Les tâches dans VS Code

21/11/2019

Ce que j’apprécie avec l’éditeur VS Code, c’est que sa prise en main est aisée. En une demi-journée, vous êtes productif. Les commandes principales sont toutes regroupées dans la toolbar latérale :...

Krita, c'est quoi ?

07/11/2019

Je dessine, retouche, peins et anime sur Photoshop depuis plus de 10 ans. C’est bien simple, j’aime Photoshop autant que je le déteste. C’est un très bon logiciel malgré ses défauts. Il est complet,...