ITSkillsCenter
Développement Web

Mocks et fixtures avec MSW en 2026 : tutoriel pas-à-pas

13 دقائق للقراءة

Le mock le plus douloureux du monde JavaScript des annees 2010 etait celui de fetch. Chaque équipe ecrivait son propre wrapper, son propre stub, sa propre logique pour intercepter les requetes — et chaque migration de client HTTP cassait la moitie des tests. Mock Service Worker (MSW) a règle le problème en attaquant le sujet a la racine : intercepter le réseau lui-même, au niveau du navigateur ou du runtime Node, sans modifier le code de l application. Ce tutoriel pas-à-pas montre comment installer MSW 2.14, écrire des handlers d’API, intégrer le tout a Vitest, et organiser les fixtures pour un projet qui depasse cinquante endpoints.

Prerequis

  • Node.js 20 ou plus, idéalement Node 24 (Krypton).
  • Un projet existant qui consomme une API HTTP via fetch, axios, ky, ou tout autre client.
  • Vitest déjà installé ou un runner equivalent. Le tutoriel sur Vitest couvre l installation initiale.
  • Niveau attendu : connaissance des promesses, des modules ES, et notions HTTP.
  • Temps total : 1h30 a 2h pour parcourir et adapter au projet.

Etape 1 — Installer MSW et generer le service worker

L’installation se fait en deux temps. D abord le paquet npm, ensuite la generation d’un fichier de service worker dans le dossier public si on veut intercepter les requetes en mode développement dans le navigateur. Pour les tests qui tournent uniquement en Node (Vitest), seule la première étape est nécessaire.

npm install --save-dev msw
npx msw init public/ --save

La première commande installé le paquet. La seconde copie un fichier mockServiceWorker.js dans public/ et ajouté une clé msw dans package.json pour memoriser ce chemin. Ce fichier est servi statiquement par votre application en développement et intercepte les requetes fetch et XMLHttpRequest. En production, il est ignore — MSW ne s active que si on l initialise explicitement.

Etape 2 — Ecrire les premiers handlers

Un handler est une fonction qui répond a une requete sur un chemin précis. La syntaxe MSW 2.x utilisé http.get, http.post, etc. (la version 1 utilisait rest.get qui est obsolète). On écrit les handlers dans un fichier dédié, qu’on partagera entre le mode développement et les tests.

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

export const handlers = [
  http.get('/api/products', () => {
    return HttpResponse.json([
      { id: 1, name: 'Dattes Medjool 1kg', price: 12.5 },
      { id: 2, name: 'Huile d olive 500ml', price: 8.0 },
      { id: 3, name: 'Miel de fleurs 250g', price: 6.5 },
    ]);
  }),

  http.get('/api/products/:id', ({ params }) => {
    const id = Number(params.id);
    if (id === 1) return HttpResponse.json({ id: 1, name: 'Dattes Medjool 1kg', price: 12.5 });
    return new HttpResponse(null, { status: 404 });
  }),

  http.post('/api/orders', async ({ request }) => {
    const body = await request.json() as { items: Array<{ sku: string; qty: number }> };
    if (body.items.length === 0) {
      return HttpResponse.json({ error: 'Panier vide' }, { status: 400 });
    }
    return HttpResponse.json({ id: 'ord_test_42', status: 'pending' }, { status: 201 });
  }),
];

Trois handlers couvrent les cas usuels : un GET de liste, un GET avec paramètre dans l’URL, un POST avec validation du body. HttpResponse.json() est l usine officielle pour produire une réponse JSON avec le bon header Content-Type. Pour répondre une 404 ou une 500, on instancie HttpResponse directement avec le statut. Cette structuré rend les handlers très lisibles, ce qui est crucial quand on en a une centaine.

Etape 3 — Brancher MSW dans Vitest

Pour les tests Vitest qui tournent en Node, on utilisé setupServer de msw/node. Le serveur MSW patch la fonction fetch globale et toutes les requetes outbound passent par les handlers. On centralise le setup dans un fichier de démarrage exécute avant chaque suite.

// src/mocks/server.ts
import { setupServer } from 'msw/node';
import { handlers } from './handlers';

export const server = setupServer(...handlers);
// src/test-setup.ts
import { afterAll, afterEach, beforeAll } from 'vitest';
import { server } from './mocks/server';

beforeAll(() => server.listen({ onUnhandledRequest: 'error' }));
afterEach(() => server.resetHandlers());
afterAll(() => server.close());

Dans vitest.config.ts, on pointe setupFiles vers src/test-setup.ts. Trois hooks sont mis en place. beforeAll démarre le serveur d interception ; l option onUnhandledRequest: 'error' fait échouer le test si on appelle un endpoint sans handler — c’est une garde précieuse contre les fuites réseau imprevues. afterEach reinitialise les handlers a leur état initial pour garantir l’isolation des tests. afterAll ferme proprement le serveur.

Etape 4 — Ecrire un test qui consomme un endpoint mocke

Le code metier qui appelle l’API n’a aucune connaissance de MSW. C est même le but : on teste le code de production tel qu’il s exécute en realite, juste avec un faux serveur derriere.

// src/lib/products.ts
export async function fetchProducts() {
  const r = await fetch('/api/products');
  if (!r.ok) throw new Error('Erreur HTTP ' + r.status);
  return r.json();
}
// src/lib/products.test.ts
import { it, expect } from 'vitest';
import { fetchProducts } from './products';

it('retourne la liste de produits', async () => {
  const products = await fetchProducts();
  expect(products).toHaveLength(3);
  expect(products[0].name).toBe('Dattes Medjool 1kg');
});

Le test fonctionne sans configuration supplémentaire grace au setup global. La requete fetch('/api/products') est interceptee par MSW, le handler retourne le JSON simule, et le code metier deserialise normalement. Si demain on remplace fetch par axios, le test continue de marcher sans aucune modification — c’est l avantage clef de mocker au niveau réseau.

Etape 5 — Surcharger un handler dans un test spécifique

Tester le chemin heureux est facile. Tester les erreurs (panier vide, 500 serveur, latence) est plus instructif. MSW permet d ajouter un handler ad-hoc dans un test, qui prend le pas sur le handler global pour la duree du test.

import { it, expect } from 'vitest';
import { http, HttpResponse } from 'msw';
import { server } from '../mocks/server';
import { fetchProducts } from './products';

it('propage l erreur quand l API renvoie 500', async () => {
  server.use(
    http.get('/api/products', () => new HttpResponse(null, { status: 500 }))
  );
  await expect(fetchProducts()).rejects.toThrow('Erreur HTTP 500');
});

it('gere correctement la latence', async () => {
  server.use(
    http.get('/api/products', async () => {
      await new Promise(r => setTimeout(r, 50));
      return HttpResponse.json([]);
    })
  );
  const result = await fetchProducts();
  expect(result).toEqual([]);
});

server.use(...) ajouté un handler temporaire. Grace au resetHandlers du afterEach, ce handler temporaire disparait après le test, et les tests suivants retrouvent les handlers de base. C est ainsi qu’on couvre tous les cas d’erreur sans polluer la suite globale.

Etape 6 — Organiser les handlers pour scaler

Au-dela de cinquante endpoints, un seul fichier handlers.ts devient illisible. La pratique recommandee est de découper par domaine fonctionnel : handlers/products.ts, handlers/orders.ts, handlers/auth.ts. Chaque fichier exporte un tableau, et un index.ts les agrege.

// src/mocks/handlers/index.ts
import { productsHandlers } from './products';
import { ordersHandlers } from './orders';
import { authHandlers } from './auth';

export const handlers = [
  ...productsHandlers,
  ...ordersHandlers,
  ...authHandlers,
];

Cette structuré permet a plusieurs développeurs de travailler sur des handlers differents sans conflit de merge. Pour aller plus loin, on peut extraire les données de seed (les listes de produits, les utilisateurs de demo) dans un dossier fixtures/ partagé, ce qui évite de dupliquer les mêmes objets entre les handlers.

Etape 7 — Activer MSW en mode développement local

MSW n’est pas seulement utile aux tests. En développement, on peut s’en servir pour faire tourner l application sans backend, ce qui est précieux quand l’API n’est pas encore prête ou tombe en panne pendant une demo.

// src/main.ts
async function enableMocks() {
  if (import.meta.env.MODE !== 'development') return;
  const { worker } = await import('./mocks/browser');
  await worker.start({ onUnhandledRequest: 'bypass' });
}

enableMocks().then(() => {
  // initialise l application normalement ici
});

Le fichier src/mocks/browser.ts ressemble a server.ts mais utilisé setupWorker de msw/browser. L’import dynamique garantit que le code MSW n entre pas dans le bundle de production. L’option onUnhandledRequest: 'bypass' laisse passer les requetes non couvertes par les handlers vers l’API reelle, ce qui est pratique pour mocker progressivement les endpoints au fur et a mesure que l on développé.

Etape 8 — Verifier que tout marche

Pour valider la mise en place complete, on lance la suite Vitest et on observé le rapport.

npm run test:run

Le terminal doit afficher tous les tests verts en moins de cinq secondes. Si un test échoue avec « request to /api/foo not handled », c’est qu un endpoint est appelé sans handler correspondant — soit on ajouté le handler, soit on relâche la garde sur ce test spécifique. Le mode UI (npm run test:ui) montre dans le navigateur l’onglet Network avec les requetes interceptees, ce qui aide a debugger.

Erreurs fréquentes

Symptome Cause probable Solution
« request to X not handled » en test URL relative qui ne matche pas le pattern Verifier la baseURL : MSW matche sur l’URL complete, ajuster les patterns en conséquence
Le worker ne démarre pas en développement Fichier mockServiceWorker.js manquant ou non servi Relancer npx msw init public/ et vérifier que public/ est bien servi par le dev server
Tests qui passent isolement, échouent en suite Handlers non resets Ajouter server.resetHandlers() dans afterEach
FormData ou multipart non reconnu Mauvaise lecture du body Utiliser await request.formData() au lieu de request.json()
Reponse mock qui ne contient pas Set-Cookie Sameite des cookies bloquée MSW respecte les règles SameSite : utiliser SameSite=None; Secure pour les tests cross-origin

Tutoriels associes

Bonnes pratiques approfondies

Une bibliothèque de handlers MSW devient vite un produit en soi : elle est consommee par les tests Vitest, par le mode développement local, par Storybook si on l utilisé pour les composants, et eventuellement par les tests Playwright via page.route(). Pour qu’elle reste maintenable, deux pratiques font la difference. La première est de typer les handlers a partir du schema OpenAPI ou GraphQL du backend. Avec openapi-typescript ou graphql-codegen, on genere des types automatiquement, et chaque handler beneficie de l autocompletion sur les params et le body. La deuxieme est de versionner les handlers comme on versionne les fixtures de base de données : un dossier v1/, v2/ si l’API exposé des versions, ou un suffixe sur les noms de fichiers.

La troisieme pratique concerne le délai d écriture. Beaucoup d équipes commencent MSW une fois que les tests existent déjà, ce qui demande de rétro-fitter chaque test. Le bon timing est plus tôt : dès qu’on a deux ou trois endpoints intégrés dans le frontend, écrire les handlers correspondants. La courbe d adoption est plus douce et la couverture grandit naturellement.

Integration MSW dans Storybook

Storybook 8 et plus inclut un addon officiel pour MSW. Chaque story d composant peut declarer ses propres handlers, ce qui permet de visualiser et tester un composant dans differents états sans un backend reel. Pour la documentation visuelle d’un design system, c’est un atout enorme : un composant Liste produits affiche en story « loading », « empty », « with errors », « with 100 items » en quelques lignes de code, sans qu un quelconque serveur ne tourne.

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

export const Loading = {
  parameters: {
    msw: { handlers: [http.get('/api/products', () => new Promise(() => {}))] }
  }
};
export const Empty = {
  parameters: {
    msw: { handlers: [http.get('/api/products', () => HttpResponse.json([]))] }
  }
};

Cette association MSW + Storybook permet aussi de faire de la regression visuelle sur les composants via Chromatic ou Playwright en mode component testing : on figure le rendu, on détecté les changements, sans jamais démarrer le backend.

MSW pour GraphQL : pattern matching sur les operations

Les APIs GraphQL exposent un seul endpoint POST sur lequel transitent toutes les operations. Mocker au niveau URL n’est plus suffisant : il faut intercepter par nom d operation. MSW fournit un namespace dédié pour cela.

import { graphql, HttpResponse } from 'msw';

export const graphqlHandlers = [
  graphql.query('GetProducts', () => {
    return HttpResponse.json({
      data: { products: [{ id: '1', name: 'Dattes' }] }
    });
  }),
  graphql.mutation('CreateOrder', ({ variables }) => {
    if (variables.items.length === 0) {
      return HttpResponse.json({ errors: [{ message: 'Panier vide' }] });
    }
    return HttpResponse.json({ data: { createOrder: { id: 'ord_42' } } });
  }),
];

Cette syntaxe matche par nom d operation, ce qui est plus stable que de matcher sur le corps de la requete. Si le frontend change un champ dans la query mais garde le nom GetProducts, le handler continue de marcher. C est exactement ce qu’on veut : un mock qui suit l intention metier, pas l implementation.

Cas particuliers : React Native, edge runtimes

MSW fonctionne dans React Native via une intégration dédiée, et s adapté aux edge runtimes (Cloudflare Workers, Vercel Edge Functions) avec quelques contournements. Le runtime classique navigateur passe par le service worker, le runtime Node passe par le patch de fetch et http, mais les edge runtimes utilisent des APIs differentes ou plus strict. Pour ces environnements, on est souvent oblige d utiliser page.route() de Playwright ou des patches manuels.

Pour Vitest qui simule un environnement edge avec environment: 'edge-runtime', MSW marche correctement depuis la version 2.6, ce qui permet de tester les fonctions edge avec les mêmes handlers que le reste du frontend. Cette uniformite est l’un des grands atouts de MSW : un seul jeu de handlers pour toutes les couches.

Ressources officielles

Pour la vue d’ensemble strategique sur les tests modernes, voir le guide principal : Tests modernes en JavaScript en 2026.

Questions fréquentes

MSW remplace-t-il vi.mock de Vitest ?
Non, ils sont complementaires. vi.mock sert a mocker un module spécifique (ex. un client maison) ou une fonction. MSW intercepte le réseau, c’est plus bas niveau et plus réaliste pour les appels HTTP. Sur un projet bien organise, on utilisé vi.mock pour les utilitaires internes et MSW pour le réseau.

Peut-on utiliser MSW avec Playwright ?
Pour les tests E2E Playwright, il est recommande d utiliser page.route() a la place, qui est l interception native du navigateur. MSW reste utile en mode développement local et dans les tests Vitest. Tenter de marier les deux dans le même test E2E ajouté de la complexite sans bénéfice clair.

Comment generer des données réalistes ?
Avec @faker-js/faker pour produire des noms, emails, addresses fictives. On combine en general MSW pour la structuré des réponses et faker pour le contenu, ce qui évite d écrire 200 lignes de fixtures a la main.

MSW augmente-t-il le bundle de production ?
Non, si on l importe dynamiquement comme dans l étape 7. Vite et Webpack font du tree-shaking et excluent le code MSW du bundle prod. A vérifier en analysant le bundle après build avec vite build --mode production.

Que faire si un endpoint legacy retourne du XML ?
MSW supporte tous les types de réponses : HttpResponse.xml(...), HttpResponse.text(...), ou un new HttpResponse(body, { headers: { 'Content-Type': 'application/xml' } }) pour le cas custom.

Sponsoriser ce contenu

Cet emplacement est à vous

Position premium en fin d'article — c'est l'instant où les lecteurs sont le plus engagés. Réservez cet espace pour votre marque, votre formation ou votre offre.

Recevoir nos tarifs
Publicité