msw TypeScript : guide complet avec exemples

Formateur Python en ligne pour débutants

Apprenez Python facilement sans surcharge théorique. Résolvez des tâches pratiques avec vérification automatique et écrivez du code directement dans le navigateur.

Start Course

msw pour TypeScript : guide complet avec exemples

Qu'est-ce que msw et pourquoi l'utiliser ?

Mock Service Worker (msw) est une bibliothèque qui intercepte et simule les requêtes réseau au niveau de l'API Service Worker. Contrairement aux solutions traditionnelles (comme axios-mock-adapter ou nock), msw fonctionne au niveau du navigateur ou de Node.js, en interceptant les véritables requêtes HTTP et en renvoyant des réponses prédéfinies. Cela permet de tester vos applications sans avoir besoin de lancer un serveur réel ou de modifier le code de l'application.

L'objectif principal de msw est de fournir un outil unifié pour simuler des API, que ce soit dans les tests navigateur (avec un vrai Service Worker) ou dans les tests serveur (Node.js). La bibliothèque est particulièrement utile pour les développeurs utilisant TypeScript, car elle offre un typage strict pour toutes les requêtes et réponses. msw est idéal pour les tests unitaires, les tests d'intégration, ainsi que pour le développement frontend isolé du backend.

L'avantage clé de msw est que les simulations fonctionnent au niveau réseau, et non au niveau du code. Cela signifie que toutes les bibliothèques effectuant des requêtes HTTP (fetch, axios, graphql-request, etc.) seront automatiquement interceptées sans configuration supplémentaire. La bibliothèque prend en charge REST API, GraphQL, et permet même de gérer les connexions WebSocket (via des extensions).

Installation

Pour installer msw dans un projet TypeScript, exécutez l'une des commandes suivantes :

# npm
npm install msw --save-dev

# yarn
yarn add msw --dev

# pnpm
pnpm add msw --save-dev

Après l'installation, vous devez initialiser le Service Worker pour une utilisation dans le navigateur :

npx msw init public/ --save

Pour une utilisation côté serveur (Node.js), aucune initialisation supplémentaire n'est nécessaire.

Démarrage rapide — exemple minimal fonctionnel

Créons une simulation simple pour une API REST. L'exemple inclut la définition d'un gestionnaire, la configuration du serveur et une requête de test.

// handlers.ts
import { http, HttpResponse } from 'msw';

export const handlers = [
  http.get('https://api.example.com/user', () => {
    return HttpResponse.json({
      id: 'c7b3d8e0-5e0b-4b0f-8b3a-3b9f4b3d3b3f',
      firstName: 'John',
      lastName: 'Maverick',
    });
  }),
];

// server.ts (pour Node.js)
import { setupServer } from 'msw/node';
import { handlers } from './handlers';

export const server = setupServer(...handlers);

// test.ts
import { server } from './server';

beforeAll(() => server.listen());
afterEach(() => server.resetHandlers());
afterAll(() => server.close());

test('devrait récupérer l\'utilisateur', async () => {
  const response = await fetch('https://api.example.com/user');
  const data = await response.json();
  expect(data.firstName).toBe('John');
});

Méthodes et fonctions principales

1. http.get()

Signature : http.get(url: string | RegExp, resolver: (info: HttpRequestInfo) => HttpResponse)

Description : Crée un gestionnaire pour les requêtes GET vers une URL spécifiée. Prend en charge les chaînes exactes, les expressions régulières et les modèles avec paramètres.

import { http, HttpResponse } from 'msw';

export const handler = http.get('https://api.example.com/users/:id', ({ params }) => {
  const { id } = params;
  return HttpResponse.json({ id, name: 'Utilisateur ' + id });
});

2. http.post()

Signature : http.post(url: string | RegExp, resolver: (info: HttpRequestInfo) => HttpResponse)

Description : Crée un gestionnaire pour les requêtes POST. Permet de lire le corps de la requête via request.json().

import { http, HttpResponse } from 'msw';

export const handler = http.post('https://api.example.com/users', async ({ request }) => {
  const body = await request.json();
  return HttpResponse.json({ id: 'nouveau', ...body }, { status: 201 });
});

3. http.put()

Signature : http.put(url: string | RegExp, resolver: (info: HttpRequestInfo) => HttpResponse)

Description : Crée un gestionnaire pour les requêtes PUT, utilisé pour mettre à jour des ressources.

import { http, HttpResponse } from 'msw';

export const handler = http.put('https://api.example.com/users/:id', async ({ params, request }) => {
  const { id } = params;
  const updates = await request.json();
  return HttpResponse.json({ id, ...updates });
});

4. http.delete()

Signature : http.delete(url: string | RegExp, resolver: (info: HttpRequestInfo) => HttpResponse)

Description : Crée un gestionnaire pour les requêtes DELETE. Renvoie généralement un statut 200 ou 204.

import { http, HttpResponse } from 'msw';

export const handler = http.delete('https://api.example.com/users/:id', ({ params }) => {
  const { id } = params;
  return HttpResponse.json({ message: `Utilisateur ${id} supprimé` });
});

5. http.all()

Signature : http.all(url: string | RegExp, resolver: (info: HttpRequestInfo) => HttpResponse)

Description : Crée un gestionnaire qui intercepte toutes les méthodes HTTP (GET, POST, PUT, DELETE, etc.) pour une URL donnée.

import { http, HttpResponse } from 'msw';

export const handler = http.all('https://api.example.com/*', () => {
  return HttpResponse.json({ message: 'Requête simulée' });
});

6. HttpResponse.json()

Signature : HttpResponse.json(body: any, init?: ResponseInit)

Description : Crée une réponse HTTP avec un corps JSON. Permet de définir le statut, les en-têtes et d'autres options de réponse.

import { HttpResponse } from 'msw';

// Réponse JSON simple
const response1 = HttpResponse.json({ data: 'valeur' });

// Réponse avec statut personnalisé
const response2 = HttpResponse.json(
  { error: 'Non trouvé' },
  { status: 404, headers: { 'X-Custom': 'valeur' } }
);

7. HttpResponse.text()

Signature : HttpResponse.text(body: string, init?: ResponseInit)

Description : Crée une réponse HTTP avec un corps texte brut.

import { HttpResponse } from 'msw';

const response = HttpResponse.text('Contenu texte', {
  headers: { 'Content-Type': 'text/plain' },
});

8. HttpResponse.xml()

Signature : HttpResponse.xml(body: string, init?: ResponseInit)

Description : Crée une réponse HTTP avec un corps XML. Définit automatiquement l'en-tête Content-Type sur application/xml.

import { HttpResponse } from 'msw';

const response = HttpResponse.xml('Message







');

9. delay()

Signature : delay(durationMs: number) => Promise<void>

Description : Fonction utilitaire pour ajouter un délai artificiel à une réponse, simulant la latence réseau.

import { http, HttpResponse, delay } from 'msw';

export const handler = http.get('https://api.example.com/slow', async () => {
  await delay(1000); // Délai d'1 seconde
  return HttpResponse.json({ message: 'Réponse retardée' });
});

10. setupServer()

Signature : setupServer(...handlers: HttpHandler[]) => SetupServerApi

Description : Configure un serveur de simulation pour les tests dans Node.js. Renvoie un objet avec les méthodes listen(), close(), resetHandlers(), et use().

import { setupServer } from 'msw/node';
import { handlers } from './handlers';

const server = setupServer(...handlers);

// Avant tous les tests
beforeAll(() => server.listen({ onUnhandledRequest: 'warn' }));

// Après chaque test
afterEach(() => server.resetHandlers());

// Après tous les tests
afterAll(() => server.close());

11. setupWorker()

Signature : setupWorker(...handlers: HttpHandler[]) => SetupWorkerApi

Description : Configure un Service Worker pour une utilisation dans le navigateur. Utilisé principalement pendant le développement.

import { setupWorker } from 'msw/browser';
import { handlers } from './handlers';

export const worker = setupWorker(...handlers);

// Démarrer le worker
worker.start();

Exemple complet : API REST avec TypeScript

Voici un exemple complet montrant comment configurer msw pour une API REST typique avec gestion des erreurs et différents codes de statut.

// api-handlers.ts
import { http, HttpResponse, delay } from 'msw';

interface User {
  id: string;
  name: string;
  email: string;
}

const users: User[] = [
  { id: '1', name: 'Alice Martin', email: 'alice@example.com' },
  { id: '2', name: 'Bob Dupont', email: 'bob@example.com' },
];

export const handlers = [
  // GET - Liste des utilisateurs
  http.get('https://api.example.com/users', async () => {
    await delay(200);
    return HttpResponse.json(users);
  }),

  // GET - Utilisateur par ID
  http.get('https://api.example.com/users/:id', async ({ params }) => {
    await delay(100);
    const user = users.find(u => u.id === params.id);
    if (!user) {
      return HttpResponse.json(
        { error: 'Utilisateur non trouvé' },
        { status: 404 }
      );
    }
    return HttpResponse.json(user);
  }),

  // POST - Créer un utilisateur
  http.post('https://api.example.com/users', async ({ request }) => {
    await delay(300);
    const body = await request.json() as Omit;
    const newUser: User = {
      id: String(users.length + 1),
      ...body,
    };
    users.push(newUser);
    return HttpResponse.json(newUser, { status: 201 });
  }),

  // PUT - Mettre à jour un utilisateur
  http.put('https://api.example.com/users/:id', async ({ params, request }) => {
    await delay(200);
    const index = users.findIndex(u => u.id === params.id);
    if (index === -1) {
      return HttpResponse.json(
        { error: 'Utilisateur non trouvé' },
        { status: 404 }
      );
    }
    const updates = await request.json() as Partial;
    users[index] = { ...users[index], ...updates };
    return HttpResponse.json(users[index]);
  }),

  // DELETE - Supprimer un utilisateur
  http.delete('https://api.example.com/users/:id', async ({ params }) => {
    await delay(150);
    const index = users.findIndex(u => u.id === params.id);
    if (index === -1) {
      return HttpResponse.json(
        { error: 'Utilisateur non trouvé' },
        { status: 404 }
      );
    }
    users.splice(index, 1);
    return HttpResponse.json({ message: 'Utilisateur supprimé' });
  }),
];

Bonnes pratiques et conseils

  • Organisez vos gestionnaires : Séparez les gestionnaires par domaine ou par fonctionnalité dans des fichiers distincts.
  • Utilisez des variables d'environnement : Pour l'URL de base de l'API, utilisez des variables d'environnement afin de basculer facilement entre les environnements.
  • Simulez les erreurs : N'oubliez pas de tester les cas d'erreur (404, 500, timeout) pour garantir la robustesse de votre application.
  • Réinitialisez les gestionnaires entre les tests : Utilisez server.resetHandlers() dans afterEach pour éviter les interférences entre les tests.
  • Utilisez des données réalistes : Employez des bibliothèques comme @faker-js/faker pour générer des données de test réalistes.
  • Profitez du typage TypeScript : Définissez des interfaces pour vos données et utilisez-les dans vos gestionnaires pour bénéficier de l'autocomplétion et de la vérification de type.

Dépannage des problèmes courants

Les requêtes ne sont pas interceptées
Vérifiez que le Service Worker est correctement enregistré (pour le navigateur) ou que server.listen() est appelé avant les tests (pour Node.js). Assurez-vous également que l'URL correspond exactement à celle définie dans le gestionnaire.
Erreur "Cannot find module 'msw/node'"
Ce problème survient généralement avec certaines configurations TypeScript. Assurez-vous que votre tsconfig.json inclut "moduleResolution": "node" ou "bundler".
Les gestionnaires ne se réinitialisent pas entre les tests
Vérifiez que server.resetHandlers() est bien appelé dans le bloc afterEach de votre configuration de test.
Problèmes avec les requêtes GraphQL
Pour GraphQL, utilisez les fonctions graphql.query() et graphql.mutation() de msw au lieu de http.get() et http.post().

Conclusion

msw est un outil puissant et flexible pour simuler des API dans vos projets TypeScript. Que vous travailliez sur des tests unitaires, des tests d'intégration ou le développement frontend, msw vous permet de créer des simulations réalistes sans modifier votre code applicatif. Grâce à son intégration native avec TypeScript et sa prise en charge de REST, GraphQL et WebSocket, msw s'impose comme la solution de référence pour la simulation réseau dans l'écosystème JavaScript.

Pour aller plus loin, consultez la documentation officielle et explorez les fonctionnalités avancées comme les gestionnaires conditionnels, les réponses dynamiques et les extensions personnalisées.

Recommendations