mswjs/msw
github.com/mswjs/msw ↗ · 517 files · 1.4 MB
Mock Service Worker The industry standard for API mocking in JavaScript.
Overview
MSW 3.0.1 is an ESM JavaScript library for describing mocked HTTP, GraphQL, WebSocket, and Server-Sent Events behavior during development and testing. Browser and Node setup APIs share a network-source, frame, and handler pipeline, while browser HTTP interception crosses a Service Worker message channel and Node interception uses @mswjs/interceptors. The inspected implementation also provides scoped Node handler overrides, persistent browser mock cookies, cross-runtime WebSocket clients, a worker-installation CLI, and Vite integration.
System map
The package exposes separate environment and protocol entrypoints around one shared network engine. Browser HTTP interception uses a Service Worker when available, with an interceptor fallback; WebSocket interception remains a separate source. Node combines four interceptor types and adds asynchronous handler contexts. Distribution consists of ESM modules, declarations, an IIFE bundle, and a generated worker asset, with release automation targeting npm.
- package.json declares ESM, Node >=22.12.0, pnpm 11.27.1, and conditional exports that reject msw/browser in Node and msw/node in browsers.
- The root entry exports HTTP, WebSocket, SSE, and utilities; GraphQL is exposed through its separate package subpath rather than the root barrel.
- defineNetwork owns readyState, resolved options, the handler controller, and lifecycle event forwarding; each source queues frames that select handlers and resolve themselves.
- setupWorker configures ServiceWorkerSource or FallbackHttpSource for HTTP and an InterceptorSource containing WebSocketInterceptor for WebSocket connections.
- setupServer combines ClientRequest, XMLHttpRequest, Fetch, and WebSocket interceptors with AsyncHandlersController, then wraps the network in the common listen/close API.
- Browser state includes a tough-cookie jar persisted under __msw-cookie-store__ and WebSocket client metadata stored in IndexedDB; Node leaves implicit cookie management to the client and uses memory when IndexedDB is unavailable.
- tsdown builds ESM entries with declarations and source maps, a Node-platform Vite integration, and the MockServiceWorker IIFE; the worker build injects package version and checksum placeholders.
- Scripts provide lint, build, Vitest projects, Playwright, module compatibility, typings, publint, and knip; scheduled/manual releases build and test before publishing through the provenance-enabled release profile.
Feature catalog
Users describe network behavior through protocol factories and standard Request/Response objects. The public APIs support runtime overrides, lifecycle observation, explicit real-network traffic, and handler cleanup. GraphQL subscriptions and SSE provide imperative streaming controls, while WebSocket links expose clients and broadcast operations. CLI and Vite integration supply the browser worker through different installation paths.
- http provides all, head, get, post, put, delete, patch, and options factories; HttpHandler accepts URL paths, regular expressions, or asynchronous predicates returning matches and optional params.
- HttpResponse decorates standard responses for mock cookies and supplies text, JSON, XML, HTML, and ArrayBuffer helpers; text and JSON helpers calculate Content-Length unless supplied explicitly.
- graphql.link(url) supplies endpoint-scoped query, mutation, operation, and subscription factories; selectors include names, RegExp, DocumentNode, typed document strings, and custom predicates.
- GraphQL subscriptions expose publish, iterable-based from, error, complete, and passthrough controls; from publishes each yielded value without automatically completing the subscription.
- ws.link exposes clients, connection listeners, broadcast, and broadcastExcept; optional ordered extensions transform the message domain and add APIs to connection events, while ws.onUpgrade is replaceable.
- sse(path, resolver) matches GET requests accepting text/event-stream and supplies client send/error/close controls plus server.connect for forwarding an actual upstream stream.
- Worker/server APIs expose events, use, resetHandlers, restoreHandlers, and listHandlers; Node adds boundary, while utilities provide bypass, passthrough, direct getResponse, and numeric/real/infinite delays.
- msw init supports a destination, --cwd, --save, and saved worker directories; msw/vite provides auto or worker-only modes, virtual:msw, development worker serving, and conditional client-build worker emission.
Data flow
Sources normalize intercepted traffic into protocol frames before shared resolution. HTTP handlers parse matching inputs and invoke resolvers in order, with handler state owning one-time and generator behavior. Browser response transport serializes across worker ports, whereas Node responds through interceptor controllers. Cookies and WebSocket client metadata have distinct persistence paths, and SSE sends encoded frames through a ReadableStream.
- The worker serializes request metadata and an ArrayBuffer body, sends REQUEST with a transferred port/body, and the page reconstructs a FetchRequest before queuing a ServiceWorkerHttpNetworkFrame.
- InterceptorSource receives request/controller pairs, stores HTTP frames in a WeakMap keyed by Request identity, and queues them; defineNetwork forwards frame events and supplies the current handler controller.
- HttpHandler.parse gathers cookies and URL params or invokes a custom predicate; predicate combines URL matching with the HTTP method, and extendResolverArgs exposes params and cookies.
- GraphQL input comes from GET query parameters, POST JSON, or multipart operations/map/files; parsing selects the requested document operation, and GraphQLHandler caches parsed results per Request.
- executeHandlers awaits handlers sequentially and stops on a response; RequestHandler owns isUsed, generator iterator/results, and lazy finalize cleanup, including returning a thrown Response as the resolver result.
- Browser mock responses cross the port as MOCK_RESPONSE using a transferable stream or buffered ArrayBuffer; the worker reconstructs Response, special-cases status zero, and buffers streamed navigation responses before committing navigation.
- Mock response cookies are forwarded to document.cookie and stored against request.url before respondWith; later credentialed requests merge document cookies, jar cookies, and explicit Cookie headers, with headers taking precedence.
- WebSocket storage persists only client id/url and reconstructs remote handles that send BroadcastChannel commands; SSE client.send instead serializes payloads into UTF-8 event frames enqueued on a response stream.
Behavior traces
The following traces follow concrete startup, response, persistence, isolation, and reload paths through the inspected source. Browser startup waits for worker activation and a client confirmation before returning a registration. HTTP resolution distinguishes explicit bypass, unhandled traffic, fallthrough, and mocked responses. Node boundaries and browser persistence preserve different kinds of state, while Vite serializes network changes around hot updates.
- Browser startup: setupWorker → start → ServiceWorkerSource.from → network.configure → network.enable → source.enable → #startWorker/getWorkerInstance → integrity check → activation wait → MOCK_ACTIVATE/MOCKING_ENABLED → return registration; unsupported Service Worker environments take FallbackHttpSource.
- Browser mock request: worker fetch handler → handleRequest/getResponse → REQUEST → #handleRequest/deserializeRequest → queue → HttpNetworkFrame.resolve/executeHandlers → RequestHandler.run → respondWith/MOCK_RESPONSE → respondWithMock; RESPONSE subsequently drives page lifecycle events.
- Unhandled HTTP flow: resolve emits request:start, checks bypass, calls executeHandlers, then executes onUnhandledFrame when no handler matches; unmatched CONNECT bypasses silently, and matched handlers returning nothing or passthrough bypass without the unhandled path.
- Node startup/request flow: setupServer creates AsyncHandlersController and defineNetwork → listen configures onUnhandledFrame and enables sources → InterceptorSource.apply/listeners → #handleRequest/queue → frame resolution → controller.respondWith; close initiates network.disable synchronously.
- Scoped override flow: server.boundary returns a wrapper → snapshots current handler buckets → AsyncLocalStorage.run creates a local context → use prepends per-kind overrides → frame lookup reads that context; outside a boundary, #getContext falls back to the server context.
- Cookie persistence flow: storeResponseCookies extracts raw Set-Cookie → CookieStore.setCookie updates tough-cookie → persist serializes jar entries to localStorage; the next browser CookieStore constructor restores entries, and getAllRequestCookies includes them only for eligible credentials.
- WebSocket flow: InterceptorSource queues a WebSocket frame → frame selects websocket handlers → handler.run matches and applies extensions → logger attaches → kConnect emits connection → link listener registers the client; broadcastExcept sends through local or remote client handles.
- Vite flow: resolveId maps virtual:msw to runtime → virtual options select browser/server defaults → runtime defines a disabled network → user configure/enable starts mocking; beforeUpdate disables, afterUpdate resumes if previously enabled, and dispose retires the instance and removes HMR listeners.
Testing signals
Tests and CI were inspected, not executed; their existence does not establish a passing result. Vitest separates unit, Node, browser, memory, typings, and CLI suites, while Playwright handles page lifecycle and multi-tab scenarios. Read assertions cover several important persistence, isolation, streaming, and teardown branches. This is a bounded inspection, and configured browser coverage is Chromium rather than evidence of cross-browser parity.
- Vitest selects adjacent src unit tests and separate Node, memory, browser, types, and e2e projects; inspected configuration establishes suite routing, but no project was executed during this investigation.
- Shared integration tests run in Node and browser unless filename suffixes exclude them; startNetwork creates the actual setupServer/setupWorker API, and its fixture defaults unhandled traffic to bypass unless options override it.
- Concurrent boundary tests assert initial/override/initial responses in separate async scopes; browser cookie tests assert document forwarding, hidden Set-Cookie headers, multiple cookies, and continued cookie inference after localStorage quota failure.
- Lifecycle assertions cover an in-flight mocked request surviving worker stop, later requests bypassing, finalize callbacks after abort including late registration, and simultaneous handler/source disposal rejection handling.
- The Node frame-retention test warms up 500 failing requests, measures 1,500 more with forced GC and heap snapshots, and allows frame growth below 5% of the measured request count; this is a regression threshold, not a measured result from this brief.
- Inspected protocol tests assert GraphQL subscription publication/completion, WebSocket extension decoding with unchanged upstream wire traffic, and SSE cessation of upstream reconnects after consumer close.
- Vite tests build and inspect production output for removal of DEV-guarded mocks and then inspect rendered application behavior; CLI tests assert copied worker files and saved paths, while typings tests expect errors for incompatible declared response bodies.
- CI specifies Node 22.23.2/24/26 with lint/build/Vitest/Playwright, compatibility jobs inspect package exports and TypeScript 5.9/6.0/7.0, and smoke automation tests external examples; browser configurations inspected here select Chromium, and no CI run status was retrieved.
Risk map
These are source observations and explicitly marked hypotheses, not confirmed vulnerabilities. Network passthrough, persistent cookies, worker messaging, and serialized handler exceptions are the principal security-sensitive boundaries inspected. Shared lifecycle and streaming code has a broad behavioral impact, with regression tests documenting past failure modes. Documentation and automation also contain drift that should be verified before relying on their instructions.
- Default warn handling permits real HTTP traffic after warning; explicit bypass and passthrough also reach the network, while unmatched CONNECT bypasses regardless of the configured unhandled strategy.
- Browser mock cookies persist as JSON in origin localStorage and are also assigned to document.cookie; only persistence writes are caught for quota errors, while the initial localStorage.getItem call is outside that catch.
- ServiceWorkerHttpNetworkFrame.errorWith converts handler exceptions into HTTP 500 JSON containing name, message, and stack; Node's interceptor frame instead throws ordinary errors, creating an environment-specific error surface.
- WorkerChannel filters messages with a non-null source unequal to the selected worker, then accepts object payloads containing type; WebSocket managers process worker-stop window messages by type and remote BroadcastChannel commands by client id.
- The worker checksum is a normalized-source MD5 compatibility marker: mismatch produces a warning and resolves the integrity promise instead of blocking startup. It should not be interpreted as an authentication or tamper-enforcement mechanism.
- WebSocketHandler emits connection events synchronously, with an explicit FIXME about propagating asynchronous listener exceptions; the frame's try/catch surrounds that synchronous emission.
- CLI destinations accept absolute paths or resolve relative paths without repository containment, then create directories and overwrite the worker file; saving replaces the msw object with workerDirectory, so unrelated msw fields are not preserved by that assignment.
- Hypothesis: TypeScript-nightly may fail to exercise the intended RC check because compare writes step outputs without declaring job outputs, while test reads needs.compare.outputs and copies a version-specific config using a questionable shell interpolation. This needs workflow validation; no failure was observed. (inferred)
Extension points
The experimental API exposes the same source/frame abstractions used by the built-in environments. Handler factories, custom predicates, controllers, and sibling graphs provide additional composition boundaries. WebSocket extensions can change both runtime messages and inferred APIs. Storage implementations and Vite hooks are identifiable implementation seams, with public export/build configuration determining what consumers can import.
- msw/experimental exports defineNetwork, NetworkSource, InterceptorSource, NetworkFrame, protocol frames, and handler controllers; a custom source implements enable and queues frames through an awaited emitter.
- Custom NetworkFrame subclasses define protocol/data, handler selection, resolve, passthrough, errorWith, and unhandled diagnostics; resolve reports handled, unhandled, or skipped with true/false/null.
- RequestHandler subclasses supply predicate/log and may override parse/extendResolverArgs; http's createHttpHandler factory constructs HttpHandler while custom HTTP predicates can calculate resolver params.
- HandlersController abstracts getState/setState and supplies grouped use/reset/restore/dispose operations; AsyncHandlersController changes state ownership using AsyncLocalStorage rather than replacing request resolution.
- attachSiblingHandlers composes hidden transport/upgrade handlers with a public owner; grouping recursively visits sibling graphs and deduplicates references, while listHandlers hides implementation siblings.
- WebSocketExtension is publicly re-exported; ws.link accepts ordered extensions, handler.run invokes apply and extend, and ws.onUpgrade is read at request time so later overrides affect existing links.
- onUnhandledFrame accepts an async callback receiving frame and defaults; predefined strategies suppress common HTTP assets, while custom callbacks must apply their own asset policy and defaults.error only prints.
- WebSocketClientStore defines add/getAll/deleteMany with memory/IndexedDB implementations selected by the manager; new public utility modules are automatically discovered by tsdown and exposed through the ./utils/* export.
Contributor handoff
Start with current manifests and the shared network engine, then follow the affected environment and protocol boundary. Tests and CI were inspected, not executed; this brief provides no passing checks or exhaustive coverage assessment. Existing scripts and filename conventions identify focused checks for a later task that authorizes execution. Preserve worker protocol compatibility, handler ordering, cleanup semantics, and build/export boundaries when making small changes.
- Use package.json and test/README.md as current workflow pointers: CONTRIBUTING.md recommends pnpm 8.15 and older test/browser paths, while the manifest pins pnpm 11.27.1 and current tests use protocol/API domains and filename suffixes.
- Before request-resolution edits, read defineNetwork, HttpNetworkFrame, executeHandlers, and RequestHandler.run together; handler order, fallthrough, once rechecks, and response dispatch span those files.
- Maintain strict TypeScript, kebab-case filenames, single quotes, and no semicolons; production source imports should target concrete modules instead of package barrels, as enforced by oxlint.
- When execution is permitted, select the smallest relevant test:unit, test:node, test:browser, test:playwright, test:e2e, or test:ts script; inspected tests and CI remain unexecuted evidence in this report.
- Build-dependent verification needs lib output: Vitest imports lib/vite/index.js and tsdown emits public entries/declarations; source export changes should be reviewed alongside package exports and module compatibility jobs.
- Worker protocol edits require paired page/worker review plus checksum generation: copyServiceWorker injects placeholders, WorkerChannel translates payload to data, and browser response transport branches between streams and buffers.
- Keep teardown synchronous at the source boundary even when handlers dispose asynchronously; worker stop intentionally retains channel handling for in-flight requests, and regression tests assert both pending-request completion and disposal rejection observation.
- Review persistence and publication helpers without invoking them during source-only work: msw init writes destination files/package.json, the smoke script changes version and clones/installs examples, and release scripts publish externally.
Languages
| Language | Files | Share of source |
|---|---|---|
| TypeScript | 443 | 98% |
| JSON | 21 | docs/config |
| YAML | 12 | docs/config |
| JavaScript | 9 | 2% |
| Markdown | 9 | docs/config |
| HTML | 7 | docs/config |
| Shell | 1 | 0% |
Start reading here
Probable source entrypoint: src/ — inferred from the directory tree and manifest, not the upstream docs.
Source roots
src/ (166 files)
Repository structure
| Top-level path | Files |
|---|---|
test/ | 298 |
src/ | 166 |
media/ | 12 |
.github/ | 11 |
config/ | 4 |
decisions/ | 4 |
cli/ | 3 |
Docs found: CONTRIBUTING.mdLICENSE.mdREADME.md
Package
package.json — msw v3.0.1, 54 declared dependencies.
@commitlint/cli@commitlint/config-conventional@epic-web/test-server@fastify/websocket@graphql-typed-document-node/core@inquirer/confirm@msw/url@mswjs/interceptors@ossjs/release@playwright/test@types/express@types/json-bigint
CI & config entrypoints
README.md— Repository readmetest/playwright.config.ts— Playwright e2e configtest/modules/browser/playwright.config.ts— Playwright e2e config.github/workflows/smoke-test.yml— GitHub Actions workflow.github/workflows/compat.yml— GitHub Actions workflow.github/workflows/release.yml— GitHub Actions workflow.github/workflows/typescript-nightly.yml— GitHub Actions workflow.github/workflows/lock-closed-issues.yml— GitHub Actions workflow.github/workflows/release-preview.yml— GitHub Actions workflow.github/workflows/ci.yml— GitHub Actions workflow
Structural health signals
Average score 8.9/10 across 96 analyzed source files; 6 hotspots surfaced.
test/setup/vitest-helpers.ts— 550 lines, score 4.5 (Large file, Deep nesting)src/mockServiceWorker.js— 460 lines, score 5.2 (Large file, Deep nesting)src/browser/sources/service-worker-source.ts— 564 lines, score 5.7 (Large file, Deep nesting)
These are deterministic review leads — size, churn, and structural signals — not proof of a bug in this project.
Recent history
6d6aef1640ac2026-09-30 — chore(release): v3.0.17cad86a03b0d2026-09-30 — fix: treat `CONNECT` requests as the transport mechanism (#2796)975cb459eeff2026-09-29 — docs: remove spacing before logo, update descriptionb6b50f061a222026-09-28 — chore(release): v3.0.032cf523b56e62026-09-28 — chore: run release job on `ubuntu-latest` to unlock provenance