Aller au contenu principal

Tester le retour json d’une API en 15 minutes avec JsonSchema (feat. Behat)

Récemment, j’ai travaillé avec un collègue pour un client qui voulait exploiter des données de capteurs pour les présenter sur un dashboard. Nos deux points forts étant respectivement le back et le front, c’est tout naturellement que nous nous sommes réparti le travail suivant ces critères. Le but était pour moi d’importer, traiter et redistribuer la donnée sous forme d’API.

Toujours dans l’idée d’apporter le maximum de valeur pour le minimum de temps (donc d’argent, rappelez vous), je me suis penché sur une validation simple du JSON retourné par mon API. Je connaissais JsonSchema et je me suis replongé dedans pour l’occasion, et j’ai été plutôt agréablement surpris de la facilité d’implémentation ! C’est parti !

Pré-requis

Nous avions déjà des tests behat d’installés, je vous passerai donc les bases. Imaginons que vous avez déjà un contexte de prêt et un projet qui tourne avec une requête qui vous renvoi du JSON (votre fameuse API).

Concept

JsonSchema : le principe est de comparer un bout de JSON avec un document “exemple”, notre schéma, qui décrit à quoi doit ressembler la donnée. C’est un concept qui n’est pas lié à un langage, pour notre cas (le PHP), on va utiliser une librairie qui l’implémente : justinrainbow/json-schema

Attention il y a plusieurs version de JsonSchema (les “drafts”). Pour la suite de cet article on parlera de la version 4.

L’idée ça va être d‘utiliser ça avec behat, donc on va voir dans un premier temps comment créer notre “schema”, puis on va rajouter une fonction à notre contexte behat pour pouvoir tester l’url voulue (celle qui renvoie les données en JSON). Dans notre scénario, on pourra utiliser cette nouvelle règle pour déclarer quelque chose du genre :

J’appelle l’url “xx” et le contenu retourné doit être valide vis à vis du schema “xx”

Développement

La première chose à faire est d’installer le vendor avec composer :

composer require justinrainbow/json-schema --dev

Si vous n’avez pas encore de contexte custom behat, c’est aussi le moment de le créer !

Ensuite on va construire notre schéma. C’est relativement simple de faire quelque chose de léger : c’est un fichier dans lequel on va dire des choses du genre :

  • Je veux avoir dans la réponse un object “water_consumption”

  • Cet object devra avoir une propriété “value” qui sera un nombre, une propriété “previousValue” qui sera aussi un nombre et enfin une propriété “evolution”, le pourcentage d’évolution entre “value” et “previousValue”

  • Ensuite je veux aussi dans la réponse un object “energy_consumption”, qui sera comme “water_consumption” : un object avec les 3 propriétés “value”, “previousValue” et “evolution”

  • … et ainsi de suite

Si vous avez déjà votre API, vous avez sûrement déjà du JSON à tester. C’est parfait. Plutôt que de tout vous taper à la main, vous pouvez utiliser ce jeu de données pour générer un premier schéma grâce à https://jsonschema.net/ (pensez à changer la version du schema suivant votre convenance).

Le schéma généré est relativement complet (avec “default”, “title” et “examples” pour toutes les propriétés), aussi pour plus d’aisance dans la lecture du schema vous pouvez retirer ces propriétés secondaires.

Créez un fichier au sein de votre dossier de tests suivant ce chemin : /Tests/JsonSchema/schema.json et copiez dedans le schéma généré par jsonschema.net

Vous avez déjà la première partie ! Bien évidement vous pouvez adapter le schéma à votre convenance. Pour ma part, plutôt que la doc originale, j’aime bien regarder sur spacetelescope.github.io pour comprendre ce que je peux faire avec.

D’ailleurs avant d’aller plus loin, vous pouvez déjà checker si votre json est valide grâce à https://www.jsonschemavalidator.net/

C’est comme ça que j’aime travailler : je copie colle mon schéma dans la textearea de gauche et mon JSON de données dans la textarea de droite, et je regarde comment je peux modifier le schéma pour faire ce que je veux (ou modifier les données pour voir comment le validateur va réagir). Une fois que je suis satisfait, je mets à jour mon fichier avec mon nouveau schéma et je relance les tests.

Pour résumer, on a maintenant un schéma qui décrit à quoi doivent ressembler les données, une API, il nous manque plus qu’à indiquer à behat de tester tout ça automatiquement !

Update merci à Jeremy Jumeau pour le tip, vous n’êtes pas obligé d’implémenter vous même la méthode dans le contexte :

Pour ma part plutôt que de créer une définition d’étape dans un contexte Behat, j’utilise Behatch/contexts : https://github.com/Behatch/contexts

Il permet, entre autres,de valider qu’une réponse JSON respecte le format spécifié dans un fichier JSON Schema: And the JSON should be valid according to the schema “tests/schemas/your-schema.json”

— Jeremy Jumeau

La doc de justinrainbow/json-schema décrit comment utiliser le vendor en PHP, mais je suis gentil, je vous propose directement une implémentation dans un contexte de behat :

<?php
use JsonSchema\Validator;|

/**
* @Then /^The request "([^"]*)" should be valid against "
*/
public function
{
$this->visitPath($url);

$content = $this->getSession()->getPage()->getContent();
$jsonContent = json_decode($content);

$validator = new Validator();
// This code assumes that you have a constant defined in
// e.g. const JSON_SCHEMA_PATH =
$validator->validate($jsonContent, (object)['$ref' =>

if ($validator->isValid()) {
echo "The supplied JSON validates against the schema.";
} else {
echo "JSON CONTENT :
echo "JSON does not validate. Violations:\n";
foreach ($validator->getErrors() as $error) {
echo sprintf("[%s] %s\n", $error['property'],
}
throw new \Exception('JSON does not validate.');
}
}

L’utilisation de cette nouvelle target est relativement simple : vous avez besoin de l’url de l’API et du nom du fichier à utiliser pour le schéma :


@web @json_schema
Feature: Test api

Scenario: Validate json schema
Given The request "/my-awesome-api" should be valid against "schema.json" json schema file

Et voilà ! C’est pas plus compliqué que ça.

De mon côté, après avoir ajouté fonctionnalité sur fonctionnalité et ayant amélioré le schéma en conséquence (merci les définitions qui permettent de mutualiser la description des propriétés), on obtient un test plutôt complet :

{
"$schema": "http://json-schema.org/draft-04/schema#",
"type": "object",
"definitions": {
"defaultData": {
"type": "object",
"properties": {
"value": {
"type": "number"
},
"previousValue": {
"type": "number"
},
"evolution": {
"type": "number"
}
},
"required": [
"value",
"previousValue",
"evolution"
]
},
"defaultMeasure": {
"allOf": [
{
"$ref": "#/definitions/defaultData"
},
{
"properties": {
"per_swimmer": {
"$ref": "#/definitions/defaultData"
}
},
"required": [
"per_swimmer"
]
}
]
},
"threshold": {
"type": "object",
"properties": {
"min": {
"type": "number"
},
"max": {
"type": "number"
},
"threshold1": {
"type": "number"
},
"threshold2": {
"type": "number"
}
},
"required": [
"min",
"max",
"threshold1",
"threshold2"
]
},
"defaultMeasureWithThreshold": {
"allOf": [
{
"$ref": "#/definitions/defaultMeasure"
},
{
"properties": {
"threshold": {
"$ref": "#/definitions/threshold"
}
}
}
]
}
},
"properties": {
"status": {
"$id": "/properties/status",
"type": "string",
"title": "The Status Schema ",
"default": "",
"examples": [
"OK"
]
},
"message": {
"$id": "/properties/message",
"type": "string",
"title": "The Message Schema ",
"default": "",
"examples": [
"Datas retrieved"
]
},
"datas": {
"$id": "/properties/datas",
"type": "object",
"properties": {
"water_total_consumption": {
"$ref": "#/definitions/defaultMeasure"
},
"water_processing_consumption": {
"$ref": "#/definitions/defaultMeasureWithThreshold"
},
"water_ecs_consumption": {
"$ref": "#/definitions/defaultMeasureWithThreshold"
},
"water_efs_consumption": {
"$ref": "#/definitions/defaultMeasureWithThreshold"
},
"water_other_consumption": {
"$ref": "#/definitions/defaultMeasureWithThreshold"
},
"water_reused": {
"allOf": [
{
"$ref": "#/definitions/defaultData"
},
{
"properties": {
"per_total_consumption": {
"$ref": "#/definitions/defaultData"
}
},
"required": [
"per_total_consumption"
]
}
]
},
"attendance_pool": {
"$ref": "#/definitions/defaultData"
},
"use_dbec": {
"type": "boolean"
}
},
"required": [
"water_total_consumption",
"attendance_pool",
"water_processing_consumption",
"water_ecs_consumption",
"water_efs_consumption"
]
}
},
"required": [
"status",
"message",
"datas"
]
}

Essayez le sur https://www.jsonschemavalidator.net/, modifiez les données regardez un peu ce qu’il ce passe ;)

Pas mal la valeur ajoutée non ?