# Tests de contrat pour l’API REST : valider les réponses par leur schéma

> Détecter automatiquement les ruptures de contrat entre votre API REST WordPress et les fronts qui la consomment, en validant chaque réponse contre un JSON Schema.

- Auteur : Clément Hadrot
- Publié le : 2023-06-09
- Mis à jour le : 2023-06-09
- Catégorie : Tests
- URL : https://wpmoderne.dev.wordpress-developpement.fr/tests/tests-contrat-api-rest-json-schema/

## L’essentiel

- Un schéma par route, versionné avec le code
- Validation automatique à chaque test PHPUnit
- Casse volontairement le build si un champ disparaît

Un vendredi après-midi, l'équipe front nous signale que l'application mobile n'affiche plus les prix des produits. Rien n'a changé côté React Native. En creusant, on retrouve le coupable : un commit qui renommait `price_html` en `formatted_price` dans un contrôleur REST maison, sans que personne côté back ne pense à prévenir le front. Les tests PHPUnit passaient tous, verts du premier au dernier. Normal : ils vérifiaient que la route répondait 200, pas la forme exacte de la réponse.

C'est ce type d'incident qui pousse à ajouter une couche de tests de contrat. L'idée n'est pas de remplacer les tests fonctionnels classiques, mais de figer la structure des réponses JSON pour que toute modification incompatible casse la suite de tests avant d'atteindre la production.

## Pourquoi un test « 200 OK » ne suffit pas

La plupart des suites de tests sur les routes de l'API REST de WordPress s'arrêtent à vérifier le code de statut et, au mieux, la présence d'une clé ou deux. C'est utile, mais ça laisse passer trois catégories de régressions silencieuses : un champ renommé, un type qui change (une chaîne qui devient un entier), et un champ qui devient optionnel alors que le front le suppose toujours présent. Aucune de ces trois régressions ne fait planter une requête HTTP. Elles cassent simplement l'appelant, souvent bien après le déploiement.

Le test de contrat consiste à décrire, une fois, la forme attendue d'une réponse sous forme de JSON Schema, puis à valider chaque réponse produite par les tests contre ce schéma. Si un champ disparaît ou change de type, la validation échoue immédiatement, avec un message explicite plutôt qu'un bug remonté trois semaines plus tard par un client.

> L'essentiel à retenir : Un schéma par route, versionné avec le code ; Validation automatique à chaque test PHPUnit ; Casse volontairement le build si un champ disparaît

## Écrire un schéma pour une route personnalisée

Prenons une route qui expose des fiches « intervenant » pour un site d'événementiel, enregistrée via `register_rest_route`. On commence par écrire le schéma dans un fichier JSON dédié, versionné dans le dépôt aux côtés du code :

```
{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "type": "object",
  "required": ["id", "name", "role", "bio", "photo_url"],
  "properties": {
    "id": { "type": "integer" },
    "name": { "type": "string", "minLength": 1 },
    "role": { "type": "string" },
    "bio": { "type": "string" },
    "photo_url": { "type": ["string", "null"], "format": "uri" },
    "social_links": {
      "type": "array",
      "items": { "type": "string", "format": "uri" }
    }
  },
  "additionalProperties": false
}
```

Le `additionalProperties: false` est volontaire : il force à documenter tout nouveau champ dans le schéma en même temps qu'on l'ajoute au contrôleur, plutôt que de le laisser filer silencieusement.

## Brancher la validation dans PHPUnit

Côté PHP, la bibliothèque `justinrainbow/json-schema` s'installe facilement via Composer et s'intègre bien dans un test `WP_UnitTestCase`. On charge le schéma, on décode la réponse, puis on valide :

```
use JsonSchema\Validator;

public function test_reponse_intervenant_respecte_le_schema() {
    $request  = new WP_REST_Request( 'GET', '/mon-site/v1/intervenants/42' );
    $response = rest_get_server()->dispatch( $request );
    $data     = json_decode( wp_json_encode( $response->get_data() ) );

    $schema    = json_decode( file_get_contents( __DIR__ . '/schemas/intervenant.json' ) );
    $validator = new Validator();
    $validator->validate( $data, $schema );

    $this->assertTrue(
        $validator->isValid(),
        implode( "\n", array_map(
            fn( $e ) => sprintf( '[%s] %s', $e['property'], $e['message'] ),
            $validator->getErrors()
        ) )
    );
}
```

Le message d'erreur en cas d'échec liste précisément les propriétés fautives, ce qui évite de rejouer la requête à la main pour comprendre ce qui a changé.

### Cas des collections et de la pagination

Pour une route qui retourne une liste, on valide chaque élément du tableau contre le schéma d'un objet unique, et on ajoute un schéma séparé pour l'enveloppe de pagination (`total`, `page`, `per_page`) exposée dans les en-têtes `X-WP-Total` et `X-WP-TotalPages`. Ces en-têtes sont accessibles via `$response->get_headers()` et méritent leur propre assertion, car ils sont souvent oubliés lors d'une refonte de contrôleur.

## Générer le schéma plutôt que l'écrire à la main

Sur les routes qui exposent déjà un `schema` au sens de l'API REST de WordPress (celui que retourne `register_rest_route` avec l'argument `schema`, consultable via une requête `OPTIONS`), on peut réutiliser directement ce schéma plutôt que d'en maintenir un second en doublon. La fonction `rest_validate_value_from_schema` fournie par le cœur permet même de valider une valeur contre ce schéma natif sans dépendance externe :

- Récupérer le schéma déclaré via `$route->get_item_schema()` dans le test
- Boucler sur les propriétés attendues et appeler `rest_validate_value_from_schema( $valeur, $schema_propriete, $nom )`
- Réserver le JSON Schema externe aux routes maison qui ne déclarent pas de schéma structuré, comme celles construites au-dessus d'un CPT sans `show_in_rest` complet

Cette approche a l'avantage de tester le schéma que le contrat documente vraiment, sans double maintenance.

## Intégrer le contrôle à la CI et informer le front

Une fois les tests de contrat en place, on les fait tourner à chaque pull request dans le même job que le reste de la suite PHPUnit. Le vrai gain arrive quand on publie aussi les schémas eux-mêmes comme artefact de build, consultable par l'équipe front : elle peut alors générer ses types TypeScript directement depuis ces fichiers JSON Schema, avec un outil comme `json-schema-to-typescript`, et détecter les incompatibilités de son côté avant même de tirer la dernière version de l'API.

> Un schéma qui vit dans un coin du dépôt et que personne ne relit sert à rien. Le vrai bénéfice vient du jour où le CI refuse de merger une PR parce qu'un champ a disparu, pas de l'existence du fichier en lui-même.

## En résumé

Les tests de contrat ne remplacent ni les tests fonctionnels, ni les tests d'intégration classiques : ils ajoutent une garantie précise sur la forme des données échangées avec les consommateurs de l'API. Pour un coût de mise en place modeste, une bibliothèque de validation, un fichier de schéma par route sensible et quelques assertions supplémentaires, on élimine une catégorie entière de régressions qui échappe aux tests habituels. Sur nos projets, c'est ce type de test qui a le mieux justifié son coût : il a détecté trois ruptures de contrat en amont dans les six mois suivant sa mise en place, contre zéro incident du même genre auparavant.
