Harness-engineering test-msw-pattern

Test MSW Pattern

install
source · Clone the upstream repo
git clone https://github.com/Intense-Visions/harness-engineering
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/Intense-Visions/harness-engineering "$T" && mkdir -p ~/.claude/skills && cp -r "$T/agents/skills/claude-code/test-msw-pattern" ~/.claude/skills/intense-visions-harness-engineering-test-msw-pattern && rm -rf "$T"
manifest: agents/skills/claude-code/test-msw-pattern/SKILL.md
source content

Test MSW Pattern

Intercept HTTP requests in tests using Mock Service Worker handlers at the network level

When to Use

  • Testing components or services that make HTTP requests
  • Mocking API responses without modifying application code
  • Simulating error responses, slow networks, or specific server behaviors
  • Sharing mock API definitions between tests and development server

Instructions

  1. Install and set up MSW:
npm install -D msw
  1. Define request handlers:
// test/mocks/handlers.ts
import { http, HttpResponse } from 'msw';

export const handlers = [
  http.get('/api/users', () => {
    return HttpResponse.json([
      { id: '1', name: 'Alice', email: 'alice@test.com' },
      { id: '2', name: 'Bob', email: 'bob@test.com' },
    ]);
  }),

  http.get('/api/users/:id', ({ params }) => {
    return HttpResponse.json({
      id: params.id,
      name: 'Alice',
      email: 'alice@test.com',
    });
  }),

  http.post('/api/users', async ({ request }) => {
    const body = await request.json();
    return HttpResponse.json({ id: 'new-id', ...body }, { status: 201 });
  }),
];
  1. Set up the server for tests:
// test/mocks/server.ts
import { setupServer } from 'msw/node';
import { handlers } from './handlers';

export const server = setupServer(...handlers);
  1. Wire into test setup:
// test/setup.ts
import { server } from './mocks/server';

beforeAll(() => server.listen({ onUnhandledRequest: 'error' }));
afterEach(() => server.resetHandlers());
afterAll(() => server.close());
  1. Override handlers per test for specific scenarios:
import { http, HttpResponse } from 'msw';
import { server } from './mocks/server';

it('shows error state when API fails', async () => {
  server.use(
    http.get('/api/users', () => {
      return HttpResponse.json(
        { message: 'Internal server error' },
        { status: 500 },
      );
    }),
  );

  render(<UserList />);

  expect(await screen.findByText('Failed to load users')).toBeInTheDocument();
});
  1. Simulate network delay:
import { delay, http, HttpResponse } from 'msw';

server.use(
  http.get('/api/users', async () => {
    await delay(2000);
    return HttpResponse.json([]);
  })
);
  1. Access request details in handlers:
http.post('/api/users', async ({ request, params, cookies }) => {
  const body = await request.json();
  const authHeader = request.headers.get('Authorization');

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

  return HttpResponse.json({ id: 'new', ...body }, { status: 201 });
}),
  1. Verify requests were made using handler inspection:
it('sends correct headers', async () => {
  let capturedHeaders: Headers;

  server.use(
    http.get('/api/users', ({ request }) => {
      capturedHeaders = request.headers;
      return HttpResponse.json([]);
    })
  );

  await fetchUsers();

  expect(capturedHeaders!.get('Accept')).toBe('application/json');
});

Details

MSW intercepts requests at the network level, below

fetch
and
XMLHttpRequest
. This means your application code is completely unmodified — no dependency injection, no mock modules, no test-specific code paths.

MSW v2 (current): Uses standard

Request
/
Response
objects from the Fetch API. Handlers use
http.get()
,
http.post()
, etc. Response construction uses
HttpResponse.json()
,
HttpResponse.text()
, etc.

onUnhandledRequest: 'error'
: Causes tests to fail if the code makes an HTTP request that no handler matches. This catches missing mocks and unintended API calls.

Handler precedence: Handlers added with

server.use()
(per-test overrides) take priority over handlers passed to
setupServer()
(defaults).
server.resetHandlers()
removes per-test overrides, restoring defaults.

Browser vs Node: MSW has two modes:

  • setupServer()
    — for Node.js test environments (Vitest, Jest)
  • setupWorker()
    — for browser environments (development server, Storybook)

Same handlers work in both modes.

Trade-offs:

  • Network-level interception is realistic — but cannot test request configuration (timeouts, retries) that happen at the HTTP client level
  • Shared handlers between dev and test reduce duplication — but test handlers should be more deterministic than dev handlers
  • onUnhandledRequest: 'error'
    catches missing mocks — but requires handlers for every request, including static assets in browser mode
  • MSW does not mock WebSocket or Server-Sent Events by default — use separate tools for real-time protocol testing

Source

https://mswjs.io/docs/

Process

  1. Read the instructions and examples in this document.
  2. Apply the patterns to your implementation, adapting to your specific context.
  3. Verify your implementation against the details and edge cases listed above.

Harness Integration

  • Type: knowledge — this skill is a reference document, not a procedural workflow.
  • No tools or state — consumed as context by other skills and agents.

Success Criteria

  • The patterns described in this document are applied correctly in the implementation.
  • Edge cases and anti-patterns listed in this document are avoided.