REST API design - 20 - Testing : tester ton API sans devenir fou

httpie, curl, Postman, tests automatises, contract testing, load testing et environnements de test pour tes APIs REST.

REST API design 21 / 25
  1. 01 REST API design - 00 - Pourquoi le design de ton API change tout
  2. 02 REST API design - 01 - Les principes REST que personne ne lit
  3. 03 REST API design - 02 - Des URLs qui ont du sens
  4. 04 REST API design - 03 - Les méthodes HTTP, pour de vrai
  5. 05 REST API design - 04 - Les codes de statut HTTP qu'il faut connaître
  6. 06 REST API design - 05 - Body, headers et le diable dans les détails
  7. 07 REST API design - 06 - La pagination, ou comment ne pas tuer ta base
  8. 08 REST API design - 07 - Filtrage et tri sans prise de tête
  9. 09 REST API design - 08 - La validation avec Zod, gardien de ton API
  10. 10 REST API design - 09 - Erreurs : un format que tes clients vont adorer
  11. 11 REST API design - 10 - Authentification : JWT, API keys et OAuth2
  12. 12 REST API design - 11 - Versioning : quand et comment faire évoluer ton API
  13. 13 REST API design - 12 - CORS : comprendre et debugger les erreurs cross-origin
  14. 14 REST API design - 13 - Rate limiting : protéger ton API sans frustrer tes clients
  15. 15 REST API design - 14 - Caching : les bonnes réponses sont celles qu'on n'envoie pas
  16. 16 REST API design - 15 - Upload de fichiers : multipart, signed URLs et streaming
  17. 17 REST API design - 16 - Relations entre ressources : embarquer, lier ou les deux
  18. 18 REST API design - 17 - HATEOAS : des liens dans tes réponses
  19. 19 REST API design - 18 - OpenAPI : le contrat entre ton API et le monde
  20. 20 REST API design - 19 - Documentation : une API non documentee est une API inutile
  21. 21 REST API design - 20 - Testing : tester ton API sans devenir fou
  22. 22 REST API design - 21 - Webhooks : quand c'est ton API qui appelle
  23. 23 REST API design - 22 - Performance : quand chaque milliseconde compte
  24. 24 REST API design - 23 - Sécurité : les attaques que tu vas subir (et comment t'en protéger)
  25. 25 REST API design - 24 - Glossaire : tous les termes REST API en un seul endroit

20 - Testing : tester ton API sans devenir fou

Ce que tu vas apprendre

  • Les outils pour tester manuellement (curl, httpie, Postman)
  • Comment écrire des tests automatises pour tes endpoints
  • Le contract testing pour vérifier la conformité a la spec
  • Le load testing pour anticiper la montee en charge
  • La gestion des environnements de test

Prerequisites

Avoir lu l'article sur la documentation. Savoir écrire des tests unitaires dans ton langage. Avoir une API REST a tester.


J'ai longtemps teste mes APIs en faisant des requêtes depuis le front. Je cliquais sur un bouton, j'ouvrais les devtools, je regardais la réponse. Quand ca cassait, je mettais un console.log cote serveur. C'est la méthode la plus lente et la moins fiable qui existe. Le jour ou j'ai écrit mes premiers tests d'intégration, j'ai gagne des heures par semaine.

Tests manuels : les outils

curl

L'outil universel. Installe partout, scriptable, mais la syntaxe est penible :

bashcurl -X POST https://api.example.com/products \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer eyJ..." \
  -d '{"name": "Burger", "price": 1200}'

httpie

curl pour les humains. La syntaxe est lisible :

bashhttp POST api.example.com/products \
  name="Burger" price:=1200 \
  Authorization:"Bearer eyJ..."

Les := indiquent une valeur non-string (nombre, booleen). Les = sont des strings. httpie ajoute automatiquement Content-Type: application/json.

Postman / Bruno

Postman est l'outil graphique de référencé. Tu créés des collections de requêtes, tu définis des variables d'environnement, tu ecris des tests en JavaScript. L'inconvenient : c'est un SaaS qui pousse vers un abonnement payant.

Bruno est l'alternative open source que je préféré. Les requêtes sont stockees dans des fichiers texte dans ton repo Git. Pas de compte, pas de cloud, tout est local.

Tests automatises

Le vrai gain, c'est l'automatisation. Pour chaque endpoint, teste le cas nominal et les cas d'erreur :

typescriptimport { describe, it, expect, beforeAll } from "vitest";

const BASE_URL = "http://localhost:3000";

describe("POST /products", () => {
  let token: string;

  beforeAll(async () => {
    const res = await fetch(`${BASE_URL}/auth/login`, {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ email: "admin@test.com", password: "test123" })
    });
    const data = await res.json();
    token = data.token;
  });

  it("cree un produit avec des donnees valides", async () => {
    const res = await fetch(`${BASE_URL}/products`, {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
        Authorization: `Bearer ${token}`
      },
      body: JSON.stringify({ name: "Burger", price: 1200 })
    });

    expect(res.status).toBe(201);
    const product = await res.json();
    expect(product.name).toBe("Burger");
    expect(product.id).toBeDefined();
  });

  it("retourne 422 si le prix est negatif", async () => {
    const res = await fetch(`${BASE_URL}/products`, {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
        Authorization: `Bearer ${token}`
      },
      body: JSON.stringify({ name: "Burger", price: -1 })
    });

    expect(res.status).toBe(422);
  });

  it("retourne 401 sans token", async () => {
    const res = await fetch(`${BASE_URL}/products`, {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ name: "Burger", price: 1200 })
    });

    expect(res.status).toBe(401);
  });
});

Avec Elysia ou Hono, tu peux tester sans lancer de serveur HTTP grâce à app.request() :

typescriptimport { app } from "../src/app";

it("liste les produits", async () => {
  const res = await app.request("/products");
  expect(res.status).toBe(200);
  const body = await res.json();
  expect(body.data).toBeInstanceOf(Array);
});

C'est plus rapide et plus simple que de démarrer un vrai serveur. J'utilise cette approche sur paltemps.fr pour tous les tests d'API.

Contract testing

Le contract testing vérifié que ta réponse est conforme au schema OpenAPI. Si ta spec dit que GET /products retourne un tableau d'objets avec id: number et name: string, le test echoue si la réponse contient un champ inattendu ou s'il en manque un.

typescriptimport SwaggerParser from "@apidevtools/swagger-parser";
import Ajv from "ajv";

const spec = await SwaggerParser.dereference("./openapi.yaml");
const ajv = new Ajv();

it("GET /products respecte le schema", async () => {
  const res = await app.request("/products");
  const body = await res.json();

  const schema = spec.paths["/products"].get.responses["200"]
    .content["application/json"].schema;

  const valid = ajv.validate(schema, body);
  expect(valid).toBe(true);
});

Ca attrape les derives entre la spec et l'implementation. Un dev ajoute un champ internal_notes sans mettre à jour la spec ? Le test echoue.

Load testing

Une API qui marche pour 10 utilisateurs ne marche pas forcement pour 1000. k6 est mon outil préféré pour le load testing :

javascript// load-test.js
import http from "k6/http";
import { check, sleep } from "k6";

export const options = {
  stages: [
    { duration: "30s", target: 50 },   // monte a 50 users
    { duration: "1m", target: 50 },     // maintient 50 users
    { duration: "10s", target: 0 }      // redescend
  ]
};

export default function () {
  const res = http.get("http://localhost:3000/products");
  check(res, {
    "status 200": (r) => r.status === 200,
    "latence < 200ms": (r) => r.timings.duration < 200
  });
  sleep(1);
}
bashk6 run load-test.js

k6 te donne les p50, p90, p99 de latence, le debit, et les erreurs. Tu decouvres vite ou sont les bottlenecks : une requête SQL lente, un N+1, un endpoint qui ne gere pas la concurrence.

Environnements de test

Ne teste jamais contre la prod. Mais ne teste pas non plus contre une base de donnees partagee ou les donnees changent entre les runs.

Stratégie 1 : base de donnees dédié par run. Chaque exécution de tests créé un schema ou une base temporaire, lance les migrations, seed les donnees, et nettoie apres.

Stratégie 2 : transactions rollback. Chaque test s'exécuté dans une transaction qui est rollback a la fin. Les donnees ne persistent jamais.

typescriptbeforeEach(async () => {
  await db.execute("BEGIN");
});

afterEach(async () => {
  await db.execute("ROLLBACK");
});

Stratégie 3 : testcontainers. Un conteneur Docker avec une base de donnees fraiche pour chaque suite de tests. Plus lourd mais complètement isole.

Résumé

  • Utilise httpie ou Bruno pour les tests manuels, curl si tu dois scripter
  • Ecris des tests automatises pour chaque endpoint : cas nominal, erreurs, authentification
  • Le contract testing garantit que ta réponse respecte la spec OpenAPI
  • Le load testing avec k6 revele les problèmes de performance avant la prod
  • Isole tes donnees de test avec des transactions rollback ou des bases temporaires

Article précédent : Documentation Article suivant : Webhooks

Sources

Réservez un audit gratuit de 30 minutes. Je vous montre concrètement ce qu'on peut automatiser.