Rechercher

PythonReactSEOPDF
GitHub Skill

msw

Mocks APIs with Mock Service Worker including request handlers, server/browser setup, and testing integration. Use when mocking APIs in tests, developing without backend, or simulating network conditions.

mgd34msu☆ 3 Étoiles du dépôt0 Forks du dépôt

Installation

Suivez les instructions du dépôt pour votre agent. Consultez les fichiers et dépendances avant l’installation.

Voir la source ↗Guides

Instructions

Mock Service Worker (MSW)

API mocking library for browser and Node.js with network-level interception.

Quick Start

Install:

npm install msw --save-dev

Project structure:

src/
  mocks/
    handlers.ts    # Request handlers
    browser.ts     # Browser setup
    server.ts      # Node.js setup

Request Handlers

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

export const handlers = [
  // GET request
  http.get('/api/users', () => {
    return HttpResponse.json([
      { id: 1, name: 'John Doe' },
      { id: 2, name: 'Jane Smith' },
    ]);
  }),

  // GET with params
  http.get('/api/users/:id', ({ params }) => {
    const { id } = params;
    return HttpResponse.json({
      id: Number(id),
      name: 'John Doe',
      email: 'john@example.com',
    });
  }),

  // POST request
  http.post('/api/users', async ({ request }) => {
    const body = await request.json();
    return HttpResponse.json(
      { id: 3, ...body },
      { status: 201 }
    );
  }),

  // PUT request
  http.put('/api/users/:id', async ({ params, request }) => {
    const { id } = params;
    const body = await request.json();
    return HttpResponse.json({ id: Number(id), ...body });
  }),

  // DELETE request
  http.delete('/api/users/:id', ({ params }) => {
    return new HttpResponse(null, { status: 204 });
  }),

  // PATCH request
  http.patch('/api/users/:id', async ({ params, request }) => {
    const { id } = params;
    const body = await request.json();
    return HttpResponse.json({ id: Number(id), ...body });
  }),
];
Query Parameters
http.get('/api/search', ({ request }) => {
  const url = new URL(request.url);
  const query = url.searchParams.get('q');
  const page = url.searchParams.get('page') || '1';

  return HttpResponse.json({
    query,
    page: Number(page),
    results: [
      { id: 1, title: `Result for "${query}"` },
    ],
  });
}),
Headers
http.get('/api/protected', ({ request }) => {
  const authHeader = request.headers.get('Authorization');

  if (!authHeader?.startsWith('Bearer ')) {
    return HttpResponse.json(
      { error: 'Unauthorized' },
      { status: 401 }
    );
  }

  return HttpResponse.json({ data: 'protected data' });
}),
Response with Headers
http.get('/api/data', () => {
  return HttpResponse.json(
    { data: 'value' },
    {
      headers: {
        'X-Custom-Header': 'custom-value',
        'Cache-Control': 'no-cache',
      },
    }
  );
}),

Node.js Setup (Testing)

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

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

beforeAll(() => server.listen({ onUnhandledRequest: 'error' }));
afterEach(() => server.resetHandlers());
afterAll(() => server.close());
// vitest.config.ts
export default defineConfig({
  test: {
    setupFiles: ['./src/test/setup.ts'],
  },
});
Jest Integration
// src/test/setup.ts
import { server } from '../mocks/server';

beforeAll(() => server.listen());
afterEach(() => server.resetHandlers());
afterAll(() => server.close());
// jest.config.js
module.exports = {
  setupFilesAfterEnv: ['./src/test/setup.ts'],
};

Browser Setup

Initialize Worker
npx msw init public/ --save
Setup Worker
// src/mocks/browser.ts
import { setupWorker } from 'msw/browser';
import { handlers } from './handlers';

export const worker = setupWorker(...handlers);
Start in Development
// src/main.tsx or src/index.tsx
async function enableMocking() {
  if (process.env.NODE_ENV !== 'development') {
    return;
  }

  const { worker } = await import('./mocks/browser');
  return worker.start({
    onUnhandledRequest: 'bypass',
  });
}

enableMocking().then(() => {
  ReactDOM.createRoot(document.getElementById('root')!).render(
    <App />
  );
});
Next.js Integration
// src/mocks/index.ts
export async function initMocks() {
  if (typeof window === 'undefined') {
    const { server } = await import('./server');
    server.listen();
  } else {
    const { worker } = await import('./browser');
    await worker.start();
  }
}
// app/providers.tsx
'use client';

import { useEffect, useState } from 'react';

export function Providers({ children }: { children: React.ReactNode }) {
  const [mockingEnabled, setMockingEnabled] = useState(false);

  useEffect(() => {
    async function enableMocking() {
      if (process.env.NODE_ENV === 'development') {
        const { initMocks } = await import('@/mocks');
        await initMocks();
      }
      setMockingEnabled(true);
    }
    enableMocking();
  }, []);

  if (!mockingEnabled) {
    return null;
  }

  return <>{children}</>;
}

GraphQL Handlers

import { graphql, HttpResponse } from 'msw';

export const handlers = [
  // Query
  graphql.query('GetUser', ({ variables }) => {
    const { id } = variables;
    return HttpResponse.json({
      data: {
        user: {
          id,
          name: 'John Doe',
          email: 'john@example.com',
        },
      },
    });
  }),

  // Mutation
  graphql.mutation('CreateUser', ({ variables }) => {
    const { input } = variables;
    return HttpResponse.json({
      data: {
        createUser: {
          id: '123',
          ...input,
        },
      },
    });
  }),

  // With custom endpoint
  graphql.link('https://api.example.com/graphql').query('GetPosts', () => {
    return HttpResponse.json({
      data: {
        posts: [
          { id: '1', title: 'Hello World' },
        ],
      },
    });
  }),
];

Testing Patterns

Override Handlers Per Test
import { http, HttpResponse } from 'msw';
import { server } from '../mocks/server';

test('handles error response', async () => {
  server.use(
    http.get('/api/users', () => {
      return HttpResponse.json(
        { error: 'Internal Server Error' },
        { status: 500 }
      );
    })
  );

  // Test error handling
  render(<UserList />);
  await screen.findByText('Error loading users');
});

test('handles empty response', async () => {
  server.use(
    http.get('/api/users', () => {
      return HttpResponse.json([]);
    })
  );

  render(<UserList />);
  await screen.findByText('No users found');
});
Delay Responses
import { delay, http, HttpResponse } from 'msw';

http.get('/api/users', async () => {
  await delay(1000); // 1 second delay
  return HttpResponse.json([{ id: 1, name: 'John' }]);
}),

// Infinite delay (for testing loading states)
http.get('/api/data', async () => {
  await delay('infinite');
  return HttpResponse.json({ data: 'never returns' });
}),
Network Errors
import { http, HttpResponse } from 'msw';

http.get('/api/users', () => {
  return HttpResponse.error();
}),
Passthrough
import { http, passthrough } from 'msw';

http.get('/api/analytics', () => {
  return passthrough();
}),

Request Assertions

import { http, HttpResponse } from 'msw';
import { server } from '../mocks/server';

test('sends correct data', async () => {
  let requestBody: any;

  server.use(
    http.post('/api/users', async ({ request }) => {
      requestBody = await request.json();
      return HttpResponse.json({ id: 1 });
    })
  );

  // Trigger the request
  await createUser({ name: 'John', email: 'john@example.com' });

  // Assert request body
  expect(requestBody).toEqual({
    name: 'John',
    email: 'john@example.com',
  });
});

Cookies

http.get('/api/session', ({ cookies }) => {
  const sessionId = cookies.sessionId;

  if (!sessionId) {
    return HttpResponse.json(
      { error: 'No session' },
      { status: 401 }
    );
  }

  return HttpResponse.json({ user: 'John' });
}),

// Set cookies in response
http.post('/api/login', () => {
  return HttpResponse.json(
    { success: true },
    {
      headers: {
        'Set-Cookie': 'sessionId=abc123; Path=/; HttpOnly',
      },
    }
  );
}),

Streaming Responses

import { http, HttpResponse } from 'msw';

http.get('/api/stream', () => {
  const encoder = new TextEncoder();
  const stream = new ReadableStream({
    start(controller) {
      controller.enqueue(encoder.encode('Hello'));
      controller.enqueue(encoder.encode(' '));
      controller.enqueue(encoder.encode('World'));
      controller.close();
    },
  });

  return new HttpResponse(stream, {
    headers: {
      'Content-Type': 'text/plain',
    },
  });
}),

Best Practices

  1. Keep handlers in separate file - Easy to maintain and reuse
  2. Use type-safe request bodies - Define interfaces
  3. Reset handlers after tests - Prevent test pollution
  4. Mock at network level - Not implementation details
  5. Use onUnhandledRequest - Catch missing handlers

Common Mistakes

Mistake Fix
Forgetting resetHandlers Add to afterEach
Wrong handler order Specific routes before generic
Missing async/await Await request.json()
Hardcoded URLs Use relative or env vars
Not initializing worker Call worker.start()

Reference Files

Exemples

Consultez les exemples du dépôt source.

Voir la source ↗

Historique source

Date d’indexation
2026-01-20
Mise à jour de l’index
2026-01-28
Branch
main

Les dates concernent ce répertoire. Consultez le dépôt pour l’historique du code.

GitHub ↗

chat Comments (0)

chat_bubble_outline

No comments yet. Be the first to share your thoughts!