Un développeur backend renomme un champ. Les tests unitaires passent. Les tests E2E passent sur staging. En production, le frontend commence à planter parce qu'il s'attendait à name, pas full_name. Le contract testing bloque ça avant même le déploiement sur staging.

Ce qu'est le contract testing

Le contract testing vérifie que deux services s'accordent sur le format de leur communication, pas seulement que chaque service fonctionne correctement seul.

Un contrat est une description formelle de ce qu'un consommateur (votre frontend ou un autre service) attend d'un fournisseur (votre API). Le consommateur génère le contrat à partir de ses propres tests. Le fournisseur vérifie qu'il peut satisfaire ces attentes sans exécuter la suite de tests complète du consommateur.

Le résultat : vous détectez les changements d'API incompatibles avant qu'ils n'atteignent le staging.

Pact : l'outil standard

Pact est la bibliothèque de contract testing la plus adoptée. Elle fonctionne en enregistrant de vraies interactions API durant les tests du consommateur, en les sauvegardant sous forme de "pacts" (fichiers JSON), et en les rejouant contre le vrai fournisseur.

Installation côté consommateur (frontend)

npm install -D @pact-foundation/pact

Écrire un test consommateur

Ce test définit ce que votre frontend attend de l'endpoint /users/1 :

import { PactV3, MatchersV3 } from '@pact-foundation/pact';
import path from 'path';

const { like, string, integer } = MatchersV3;

const provider = new PactV3({
  consumer: 'frontend',
  provider: 'user-api',
  dir: path.resolve(process.cwd(), 'pacts'),
});

describe('User API contract', () => {
  it('returns user data', async () => {
    await provider
      .given('user 1 exists')
      .uponReceiving('a request for user 1')
      .withRequest({
        method: 'GET',
        path: '/users/1',
        headers: { Authorization: like('Bearer token123') },
      })
      .willRespondWith({
        status: 200,
        headers: { 'Content-Type': 'application/json' },
        body: {
          id: integer(1),
          name: string('Alice'),
          email: string('alice@example.com'),
        },
      })
      .executeTest(async (mockServer) => {
        const response = await fetch(`${mockServer.url}/users/1`, {
          headers: { Authorization: 'Bearer token123' },
        });
        const data = await response.json();

        expect(data.name).toBeDefined();
        expect(data.email).toBeDefined();
      });
  });
});

Quand ce test s'exécute, Pact :

1. Démarre un serveur mock

2. Enregistre l'interaction (requête + réponse attendue) sous forme de fichier pact dans ./pacts/

3. Exécute votre test contre le serveur mock

Le fichier pact généré est le contrat. Il ressemble à ceci :

{
  "consumer": { "name": "frontend" },
  "provider": { "name": "user-api" },
  "interactions": [{
    "description": "a request for user 1",
    "request": { "method": "GET", "path": "/users/1" },
    "response": {
      "status": 200,
      "body": { "id": 1, "name": "Alice", "email": "alice@example.com" }
    }
  }]
}

Vérification côté fournisseur (backend)

L'équipe backend prend ce fichier pact et vérifie que leur API le satisfait :

import { Verifier } from '@pact-foundation/pact';

describe('Provider verification', () => {
  it('validates the expectations of Frontend', () => {
    return new Verifier({
      provider: 'user-api',
      providerBaseUrl: 'http://localhost:3001',
      pactUrls: [path.resolve(process.cwd(), 'pacts/frontend-user-api.json')],
      stateHandlers: {
        'user 1 exists': async () => {
          // initialiser l'utilisateur test en base de données
          await db.users.create({ id: 1, name: 'Alice', email: 'alice@example.com' });
        },
      },
    }).verifyProvider();
  });
});

Le vérificateur envoie la requête enregistrée au vrai backend et vérifie que la réponse correspond au contrat. Si l'équipe backend renomme name en full_name, cette vérification échoue avant qu'un seul changement ne soit livré en staging.

La place de Playwright

Playwright gère les tests d'intégration E2E. Le contract testing gère la vérification aux frontières entre services. Ils se complètent.

En pratique, les contract tests détectent les renommages de champs, les champs supprimés, les changements de codes de statut et les incompatibilités de types. Les tests E2E Playwright couvrent les problèmes de rendu UI, les ruptures de flux utilisateur et les échecs d'intégration full-stack.

Les contract tests ne remplacent pas les tests Playwright. Vous les utilisez pour rendre vos tests Playwright plus rapides et plus stables. Moins d'échecs E2E proviennent de changements de format d'API qui auraient dû être détectés plus tôt.

Pact Broker : partager les contrats entre équipes

Quand consommateur et fournisseur sont dans des dépôts séparés (courant dans les microservices), il faut un endroit central pour stocker et partager les fichiers pact. Pact Broker est l'outil standard : open source, auto-hébergeable, ou disponible sous forme de PactFlow (leur service cloud).

Les équipes publient les pacts après l'exécution des tests consommateurs. Les pipelines des fournisseurs récupèrent le dernier pact et le vérifient avant de déployer. Cela crée une vérification de dépendance : le fournisseur ne peut pas déployer s'il casse le contrat d'un consommateur.

Quand le contract testing vaut l'investissement

Le contract testing génère de la charge, car il faut écrire des tests consommateurs dans le DSL de Pact, configurer les handlers d'état du fournisseur et maintenir un broker. Ça vaut le coup quand :

  • Plusieurs applications frontend ou services appellent la même API
  • Les changements d'API cassent fréquemment les consommateurs en aval
  • Vous êtes dans une architecture microservices où les équipes déploient indépendamment
  • Votre suite E2E est lente et vous voulez détecter les échecs d'intégration plus tôt

Ça ne vaut probablement pas la peine dans un monolithe où frontend et backend partagent le même dépôt et déploient ensemble. Une équipe de deux personnes ou un projet en phase précoce avec une API trop instable sont dans le même cas.

Commencez avec Playwright pour l'E2E. Ajoutez le contract testing quand vous avez deux services déployés indépendamment qui cassent mutuellement leurs hypothèses.

→ See also: Tests d'API avec l'APIRequestContext de Playwright (Sans Postman) | Tests d'API 101: Tout ce que Chaque Ingénieur QA Doit Savoir en 2026 | La Pyramide des Tests Expliquée pour les Ingénieurs QA