AI Tools Compared

Introduction

Mock Service Worker (MSW) is a powerful API mocking library that intercepts network requests at the service worker level. When combined with Claude Code, it creates a development environment where you can simulate API responses without relying on external servers. This guide walks you through setting up MSW with Claude Code and using it effectively in your development workflow.

MSW works by intercepting requests at the network level using service workers, making it indistinguishable from real network calls. This approach provides more realistic testing conditions compared to traditional mocking libraries that modify global fetch or XMLHttpRequest objects directly.

Setting Up MSW in Your Project

Before integrating with Claude Code, you need to add MSW to your project. The installation process differs slightly between JavaScript and TypeScript projects, but the core concepts remain the same.

Install MSW as a development dependency using your package manager:

npm install msw --save-dev

Initialize MSW to generate the service worker files:

npx msw init ./public --save

This command creates the necessary service worker files in your public directory. The initialization process sets up the interception layer that MSW uses to intercept and mock network requests.

Creating Mock Handlers

MSW uses handlers to define how network requests should be mocked. These handlers intercept requests matching specific patterns and return custom responses. Create a dedicated handlers file to organize your mock definitions.

Define your first handler using the REST API syntax:

import { http, HttpResponse } from 'msw';

export const handlers = [
  http.get('https://api.example.com/users', () => {
    return HttpResponse.json([
      { id: 1, name: 'John Doe', email: 'john@example.com' },
      { id: 2, name: 'Jane Smith', email: 'jane@example.com' },
    ]);
  }),
  http.post('https://api.example.com/users', async ({ request }) => {
    const newUser = await request.json();
    return HttpResponse.json(newUser, { status: 201 });
  }),
];

Each handler specifies the HTTP method, URL pattern, and response. The request handler receives the actual request object, allowing you to access headers, body, and parameters for dynamic response generation.

Configuring the Service Worker

To activate MSW in your application, you need to set up the service worker during application startup. Create a browser-specific setup file that initializes the service worker in development and testing environments.

Set up the MSW worker in your application entry point:

async function enableMocking() {
  if (typeof window === 'undefined') {
    return;
  }

  if (process.env.NODE_ENV === 'development' || process.env.NODE_ENV === 'test') {
    const { worker } = await import('./mocks/browser');
    return worker.start({
      onUnhandledRequest: 'bypass',
    });
  }
}

enableMocking().then(() => {
  console.log('MSW started successfully');
});

This setup ensures that MSW only activates in appropriate environments while passing through real requests in production. The onUnhandledRequest: 'bypass' option allows unmatched requests to proceed normally, preventing your application from breaking when testing against real APIs.

Using MSW with Claude Code

Claude Code can use MSW mocks to test API interactions without external dependencies. When working on features that involve API calls, you can define mock responses that simulate various scenarios including success cases, errors, and edge cases.

Request Claude Code to create mock scenarios:

Create handlers for a product catalog API that includes:
- GET /products (list all products)
- GET /products/:id (single product)
- POST /products (create product)
- Include pagination support
- Simulate network delays of 500ms
- Add error handling for 404 and 500 responses

Claude Code will generate appropriate handlers that you can integrate into your mock setup. This approach accelerates development by providing realistic API behavior without requiring a backend server.

Testing Strategies with MSW

MSW excels at enabling deterministic testing scenarios. By controlling exactly what responses the API returns, you can test specific code paths that would otherwise be difficult to trigger.

Test error handling with structured error responses:

http.get('https://api.example.com/protected-resource', () => {
  return HttpResponse.json(
    { error: 'Unauthorized', message: 'Invalid token' },
    { status: 401 }
  );
});

Test loading states by adding artificial delays:

http.get('https://api.example.com/slow-endpoint', async () => {
  await new Promise(resolve => setTimeout(resolve, 2000));
  return HttpResponse.json({ data: 'delayed response' });
});

These patterns allow you to verify that your application handles various API conditions gracefully, improving overall robustness.

Organizing Mock Files

As your project grows, organizing mocks becomes essential. Create a structured approach that separates concerns and makes mocks easy to maintain.

Recommended file structure for larger projects:

src/
  mocks/
    browser.js          # Worker initialization
    handlers/
      index.js          # Re-exports all handlers
      users.js          # User-related endpoints
      products.js       # Product-related endpoints
      auth.js           # Authentication endpoints
    data/
      users.json        # Static mock data
      products.json     # Product data

This organization allows you to import only the handlers needed for specific test scenarios, keeping your test suites focused and fast.

Advanced Handler Patterns

MSW supports sophisticated matching patterns that go beyond simple URL and method matching. For example, you can match requests by headers, query parameters, or request body content:

import { http, HttpResponse } from 'msw';

// Match by query parameter
http.get('https://api.example.com/search', ({ request }) => {
  const url = new URL(request.url);
  const query = url.searchParams.get('q');

  if (query?.length < 2) {
    return HttpResponse.json({
      error: 'Query must be at least 2 characters',
      results: []
    }, { status: 400 });
  }

  return HttpResponse.json({ results: [...] });
}),

// Match by request header
http.post('https://api.example.com/admin/users', ({ request }) => {
  const authHeader = request.headers.get('authorization');

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

  return HttpResponse.json({ success: true }, { status: 201 });
}),

// Match by request body
http.post('https://api.example.com/validate-email', async ({ request }) => {
  const body = await request.json();
  const isValid = /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(body.email);

  return HttpResponse.json({ valid: isValid });
}),

These patterns enable you to test edge cases and error conditions that would be difficult to reproduce with a real API.

Debugging Mock Failures

When tests fail with MSW configured, Claude Code can help diagnose issues. Common problems include unmatched requests, incorrect response shapes, or timing issues.

Enable request logging to see what requests your application actually makes:

import { http, HttpResponse } from 'msw';
import { setupServer } from 'msw/node';

const handlers = [
  http.get('*', ({ request }) => {
    console.log('Unhandled request:', request.method, request.url);
    return HttpResponse.json({ error: 'Not mocked' }, { status: 404 });
  }),
];

const server = setupServer(...handlers);

The wildcard handler at the end catches any unmatched requests and logs them. This reveals whether your application is making requests you forgot to mock.

Integration with React Testing Library

MSW pairs exceptionally well with React Testing Library. Rather than mocking fetch directly, MSW intercepts at the service worker level, making tests more realistic:

import { render, screen, waitFor } from '@testing-library/react';
import { setupServer } from 'msw/node';
import { http, HttpResponse } from 'msw';
import UserProfile from './UserProfile';

const server = setupServer(
  http.get('https://api.example.com/user/:id', () => {
    return HttpResponse.json({
      id: '123',
      name: 'Alice',
      email: 'alice@example.com'
    });
  })
);

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

test('displays user profile', async () => {
  render(<UserProfile userId="123" />);

  await waitFor(() => {
    expect(screen.getByText('Alice')).toBeInTheDocument();
  });
});

test('handles loading state', async () => {
  server.use(
    http.get('https://api.example.com/user/:id', async () => {
      await new Promise(resolve => setTimeout(resolve, 100));
      return HttpResponse.json({ id: '123', name: 'Bob' });
    })
  );

  render(<UserProfile userId="123" />);
  expect(screen.getByRole('status', { name: /loading/i })).toBeInTheDocument();
});

This approach tests your component’s actual behavior without brittle implementation details.

Performance Considerations

MSW has minimal performance overhead, but handling thousands of requests in test suites can slow execution. Optimize by:

  1. Using request handlers strategically - Only mock endpoints your test actually calls
  2. Avoiding artificial delays - Remove setTimeout from handlers once development is complete
  3. Resetting handlers between tests - server.resetHandlers() prevents state leakage
  4. Using request scope handlers - Override global handlers for specific tests without affecting others:
test('handles API errors', async () => {
  server.use(
    http.get('https://api.example.com/data', () => {
      return HttpResponse.json({ error: 'Server error' }, { status: 500 });
    })
  );

  // Test error handling
});

Best Practices

When using MSW with Claude Code, follow these practices to maximize effectiveness. First, keep mocks close to the code they test by co-locating handler definitions with their corresponding test files. Second, use environment variables to toggle mocks on and off, ensuring you can switch between mocked and real APIs easily.

Third, version your mock definitions alongside your API contracts. When your backend team updates an endpoint, update the corresponding mock to reflect the change. Fourth, document mock behavior clearly so team members understand what responses to expect.

Finally, use MSW’s request matching capabilities to create dynamic responses based on query parameters, headers, or request body content. This flexibility allows you to test complex scenarios without creating multiple handler variants.

Comparison with Alternatives

Tool Setup Complexity Realism Context-Aware
MSW Moderate Very High Yes
Jest Mocks Low Medium No
Sinon Moderate Medium Yes
node-fetch-mock Low Medium No
Vitest Mock Low Medium No

MSW stands out for its service-worker-level interception, making mocked requests indistinguishable from real network calls. This realism catches more bugs than traditional mocking approaches.

Built by theluckystrike — More at zovo.one