Skip to content
Back to skills

Msw Handlers

ASecurity

Authors Mock Service Worker (MSW) request handlers for both browser and Node.js test environments using the `http.get` / `http.post` / `HttpResponse.json` API, wires them via `setupWorker` (browser) or `setupServer` (Node), and manages the test lifecycle (`server.listen` / `resetHandlers` / `close`). Use when the project uses JavaScript / TypeScript and needs to mock fetch / XHR at the network layer for both Vitest / Jest unit tests and Cypress / Playwright integration tests.

  • 10 stars
  • 0 votes
  • 0 copies
  • 1 view
  • Added October 12, 2026
testingjavascripttypescriptgojavabashnodegitapi

Works with

  • cli
  • api

Security analysis

A96/100
  • mediumInstalls packages at runtime which could introduce malicious dependencies

Pro scans all 20 files and shows the line behind each finding

Scanned October 12, 2026

npx -y skills add testland/qa --skill msw-handlers --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Msw Handlers?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Msw Handlers
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/testland-msw-handlers/badge)](https://www.skillsdirectory.com/skills/testland-msw-handlers)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: msw-handlers
description: "Authors Mock Service Worker (MSW) request handlers for both browser and Node.js test environments using the `http.get` / `http.post` / `HttpResponse.json` API, wires them via `setupWorker` (browser) or `setupServer` (Node), and manages the test lifecycle (`server.listen` / `resetHandlers` / `close`). Use when the project uses JavaScript / TypeScript and needs to mock fetch / XHR at the network layer for both Vitest / Jest unit tests and Cypress / Playwright integration tests."
---

# msw-handlers

## Overview

Mock Service Worker (MSW) intercepts HTTP requests at the network
layer using **Service Workers in the browser** and a **request-
interception adapter in Node.js** ([msw-getting-started][gs]). The
same handler set works for both - the test author writes one
`http.get` mock and uses it from Vitest unit tests AND from a
Cypress browser test.

[gs]: https://mswjs.io/docs/getting-started

## When to use

- The project is JavaScript / TypeScript.
- Tests need to mock HTTP at the **same layer as the SUT** - 
  intercepting actual `fetch` / `XHR` calls rather than stubbing
  the HTTP client library.
- Both browser tests (Cypress / Playwright) and Node tests (Vitest /
  Jest) need the same mock data - MSW's cross-environment
  consistency is its key advantage.
- The team wants per-test handler overrides (`server.use(...)`)
  layered on top of project-wide defaults.

If the project is JVM, use `wiremock-stubs`.
For multi-protocol mocking (TCP / SMTP / LDAP / gRPC), see the
Mountebank reference in `wiremock-stubs`.

## Install

```bash
npm i msw --save-dev
```

(Per [msw-getting-started][gs].)

For browser setup, also generate the worker script:

```bash
npx msw init public/ --save
```

This places `mockServiceWorker.js` in your public directory; the
worker script is what the browser registers to intercept requests.

## Authoring handlers

Per [msw-getting-started][gs]:

```javascript
// src/mocks/handlers.js
import { http, HttpResponse } from 'msw';

export const handlers = [
  http.get('https://api.example.com/user', () => {
    return HttpResponse.json({ id: 'abc-123', firstName: 'John' });
  }),

  http.post('https://api.example.com/orders', async ({ request }) => {
    const body = await request.json();
    return HttpResponse.json({ order_id: 42, ...body }, { status: 201 });
  }),

  http.get('https://api.example.com/orders/:id', ({ params }) => {
    return HttpResponse.json({ order_id: Number(params.id), status: 'shipped' });
  }),
];
```

Handler signature: `http.<verb>(url, resolver)`. The resolver
receives `{ request, params, cookies }` and returns an
`HttpResponse`.

### `HttpResponse` static helpers

| Helper                        | Effect                                                |
|-------------------------------|-------------------------------------------------------|
| `HttpResponse.json(body, init?)` | JSON response with `Content-Type: application/json`. |
| `HttpResponse.text(body, init?)` | Plain text response.                                  |
| `HttpResponse.error()`         | Network-level error (e.g. simulate offline).         |
| `new HttpResponse(...)`        | Full-control raw response.                            |

The `init` object accepts `status`, `statusText`, `headers` - 
matching the standard `Response` API.

### Pattern matching

| Pattern                                  | Notes                                              |
|------------------------------------------|----------------------------------------------------|
| `'https://api.example.com/orders/:id'`    | Path param exposed via `params.id`.                |
| `'/api/orders'`                            | Same-origin path; matches relative to current host. |
| `'*\\/orders'`                              | Wildcards via standard URL pattern.                |
| `/^\\/api\\/orders\\/[0-9]+$/`              | Regex match.                                        |

## Browser setup - `setupWorker`

```javascript
// src/mocks/browser.js
import { setupWorker } from 'msw/browser';
import { handlers } from './handlers';

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

// Start the worker only in development / test
if (process.env.NODE_ENV !== 'production') {
  worker.start();
}
```

(Per [msw-getting-started][gs].)

In a Storybook + MSW combo, kick off `worker.start()` from the
preview file. For Cypress: register the worker before any test
that depends on the mocks.

## Node setup - `setupServer`

```javascript
// src/mocks/node.js
import { setupServer } from 'msw/node';
import { handlers } from './handlers';

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

```javascript
// vitest.setup.js (or jest.setup.js)
import { server } from './src/mocks/node';

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

(Per [msw-getting-started][gs].)

| Hook                  | When                               | Purpose                              |
|-----------------------|------------------------------------|--------------------------------------|
| `server.listen()`     | `beforeAll`                        | Activate mocking.                    |
| `server.resetHandlers()` | `afterEach`                     | Clear per-test overrides.            |
| `server.close()`      | `afterAll`                         | Tear down; release resources.        |
| `server.use(...)`     | Per-test                           | Add / override handlers for the current test only. |

`onUnhandledRequest: 'error'` is the canonical strict mode - fails
the test if the SUT makes any HTTP call that doesn't have a
matching handler. Catches "we forgot to mock that endpoint" silently
passing.

## Per-test handler overrides

```javascript
import { http, HttpResponse } from 'msw';
import { server } from './src/mocks/node';

test('handles 500 error gracefully', async () => {
  server.use(
    http.get('https://api.example.com/user', () =>
      new HttpResponse(null, { status: 500 })
    )
  );

  const result = await fetchUser();
  expect(result.error).toBe('server error');
});
// `server.resetHandlers()` in afterEach reverts to the default handlers
```

This pattern is the canonical way to test error / edge paths
without polluting the default-handlers set.

## CI integration

```yaml
# .github/workflows/test.yml
- run: npm ci
- run: npm test          # Vitest / Jest pick up vitest.setup / jest.setup automatically
```

For Cypress + MSW, also run `npx msw init` in CI to ensure the
service worker script is in `public/`.

## Anti-patterns

| Anti-pattern                                              | Why it fails                                                       | Fix |
|-----------------------------------------------------------|---------------------------------------------------------------------|-----|
| `onUnhandledRequest: 'bypass'` (the default before strict mode) | Test passes despite the SUT calling unmocked endpoints; real network bleeds in. | Always `'error'` in CI. |
| Per-test handler definition (no shared `handlers.js`)      | Duplication; drift between tests; hard to maintain.                | One `handlers.js` per project; `server.use()` for overrides. |
| Forgetting `resetHandlers()`                              | Handlers leak across tests; later test fails because earlier test's override is still active. | Always `afterEach(() => server.resetHandlers())`. |
| Using MSW for tests that exercise network errors only      | MSW intercepts at HTTP; for low-level network errors, use `HttpResponse.error()` or simulate via the test framework. | The two are different layers; MSW handles HTTP, lower errors need other tooling. |
| Mocking the same endpoint in browser AND node setups separately | Drift; bug fix in one is missed in the other. | Single `handlers.js` consumed by both `setupWorker` and `setupServer`. |
| Including MSW in the production bundle                    | Bloat; possibly leaks mocks to real users.                         | Tree-shake by importing only in `process.env.NODE_ENV !== 'production'` branches; never `import { worker }` at top level. |

## Limitations

- **Service Worker scope.** Browser MSW only intercepts requests
  from the same origin / path scope as the worker registration.
  Cross-origin requests need explicit handler URLs.
- **Streaming responses.** SSE / chunked responses work but are
  more delicate than JSON; consult MSW docs for the streaming API.
- **No native scenario state.** Stateful workflows ("after POST,
  GET returns the new item") need to manage state in your handlers
  manually (e.g. with a closure-scoped variable).

## References

- [msw-getting-started][gs] - install, handler authoring, browser
  vs node setup, lifecycle hooks, `server.use()` per-test overrides.
- MSW Docs - https://mswjs.io/docs/
- `wiremock-stubs` - JVM counterpart; its references/mountebank.md
  covers the multi-protocol alternative.

Files in this skill

  • .tessl-plugin/plugin.json608 B
  • SKILL.md8.8 KB
  • evals/browser-mocks-wont-run-in-node/criteria.json4.2 KB
  • evals/browser-mocks-wont-run-in-node/task.md3.9 KB
  • evals/cart-flow-never-sees-added-items/criteria.json4 KB
  • evals/cart-flow-never-sees-added-items/task.md4.1 KB
  • evals/fetch-stub-hides-request-bug/criteria.json4.4 KB
  • evals/fetch-stub-hides-request-bug/task.md4.1 KB
  • evals/handlers-leak-across-tests/criteria.json3.9 KB
  • evals/handlers-leak-across-tests/task.md3.5 KB
  • evals/legacy-mock-definitions-dont-run/criteria.json4 KB
  • evals/legacy-mock-definitions-dont-run/task.md4.4 KB
  • evals/mock-payloads-shipped-to-users/criteria.json3.9 KB
  • evals/mock-payloads-shipped-to-users/task.md3.5 KB
  • evals/retry-and-offline-branches-uncovered/criteria.json4.2 KB
  • evals/retry-and-offline-branches-uncovered/task.md3.8 KB
  • evals/same-fake-account-pasted-in-every-file/criteria.json3.8 KB
  • evals/same-fake-account-pasted-in-every-file/task.md5.3 KB
  • evals/two-mock-sets-drifted-apart/criteria.json3.7 KB
  • evals/two-mock-sets-drifted-apart/task.md3.9 KB

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.