Seamless REST/GraphQL API mocking library for browser and Node.js.
npx @tessl/cli install tessl/npm-msw@2.11.0Mock Service Worker (MSW) is an API mocking library that intercepts HTTP, GraphQL, and WebSocket requests at the network level using Service Workers in browsers and low-level interception in Node.js. Unlike traditional mocking libraries that stub fetch or axios functions, MSW operates transparently without modifying application code, allowing developers to reuse the same mock definitions across unit tests, integration tests, end-to-end tests, and local development.
npm install mswimport { http, HttpResponse, setupWorker } from "msw";
import { setupServer } from "msw/node";For CommonJS:
const { http, HttpResponse, setupWorker } = require("msw");
const { setupServer } = require("msw/node");Environment-specific imports:
// Browser-specific
import { setupWorker } from "msw/browser";
// Node.js-specific
import { setupServer } from "msw/node";
// React Native-specific
import { setupServer } from "msw/native";
// Core-only imports
import { http } from "msw/core/http";
import { graphql } from "msw/core/graphql";
import { ws } from "msw/core/ws";import { http, HttpResponse, setupWorker } from "msw";
// Define request handlers
const handlers = [
// REST API handlers
http.get('/api/user', () => {
return HttpResponse.json({ name: 'John Doe', id: 123 });
}),
http.post('/api/login', async ({ request }) => {
const { username, password } = await request.json();
if (username === 'admin' && password === 'secret') {
return HttpResponse.json({ token: 'abc123' });
}
return HttpResponse.json(
{ error: 'Invalid credentials' },
{ status: 401 }
);
}),
// GraphQL handlers
graphql.query('GetUser', () => {
return HttpResponse.json({
data: { user: { name: 'John Doe' } }
});
}),
];
// Browser setup
const worker = setupWorker(...handlers);
worker.start();
// Node.js setup (for testing)
const server = setupServer(...handlers);
server.listen();MSW is built around several key components:
setupWorker, setupServer) that configure and control request interceptionHttpResponse class and utilities for creating realistic mock responsesIntercept and mock HTTP requests using Express-like routing syntax with support for parameters, wildcards, and custom predicates.
interface HttpRequestHandler {
<Params, RequestBodyType, ResponseBodyType>(
predicate: string | RegExp | ((info: { request: Request; parsedResult: any }) => boolean),
resolver: (info: {
request: Request;
params: Params;
cookies: Record<string, string>;
}) => Response | Promise<Response>,
options?: { once?: boolean }
): HttpHandler;
}
const http: {
all: HttpRequestHandler;
get: HttpRequestHandler;
post: HttpRequestHandler;
put: HttpRequestHandler;
delete: HttpRequestHandler;
patch: HttpRequestHandler;
head: HttpRequestHandler;
options: HttpRequestHandler;
};Intercept and mock GraphQL operations by query/mutation name or operation type, with support for typed responses and scoped endpoints.
interface GraphQLRequestHandler {
<Query, Variables>(
predicate: string | DocumentNode | ((info: { query: any; variables: any }) => boolean),
resolver: (info: {
query: Query;
variables: Variables;
request: Request;
}) => Response | Promise<Response>,
options?: { once?: boolean }
): GraphQLHandler;
}
const graphql: {
query: GraphQLRequestHandler;
mutation: GraphQLRequestHandler;
operation: (resolver: any) => GraphQLHandler;
link: (url: string) => typeof graphql;
};Intercept and mock WebSocket connections with event-based API for handling connection lifecycle and message broadcasting.
interface WebSocketLink {
clients: Set<WebSocketClient>;
addEventListener<EventType extends keyof WebSocketHandlerEventMap>(
event: EventType,
listener: (...args: WebSocketHandlerEventMap[EventType]) => void
): WebSocketHandler;
broadcast(data: WebSocketData): void;
broadcastExcept(
clients: WebSocketClient | WebSocketClient[],
data: WebSocketData
): void;
}
const ws: {
link: (url: string) => WebSocketLink;
};Service Worker-based request interception for browser environments with lifecycle control and runtime handler management.
interface SetupWorker {
start(options?: StartOptions): Promise<ServiceWorkerRegistration | undefined>;
stop(): void;
use(...handlers: RequestHandler[]): void;
resetHandlers(...handlers?: RequestHandler[]): void;
restoreHandlers(): void;
listHandlers(): ReadonlyArray<RequestHandler>;
events: LifeCycleEventEmitter;
}
function setupWorker(...handlers: RequestHandler[]): SetupWorker;Low-level request interception for Node.js environments using HTTP/HTTPS module patching, ideal for testing scenarios.
interface SetupServer {
listen(options?: { onUnhandledRequest?: 'bypass' | 'warn' | 'error' }): void;
close(): void;
use(...handlers: RequestHandler[]): void;
resetHandlers(...handlers?: RequestHandler[]): void;
restoreHandlers(): void;
listHandlers(): ReadonlyArray<RequestHandler>;
events: LifeCycleEventEmitter;
}
function setupServer(...handlers: RequestHandler[]): SetupServer;Request interception for React Native applications using fetch and XMLHttpRequest interceptors, compatible with popular HTTP libraries.
interface SetupServerCommonApi {
listen(options?: { onUnhandledRequest?: 'bypass' | 'warn' | 'error' }): void;
close(): void;
use(...handlers: RequestHandler[]): void;
resetHandlers(...handlers?: RequestHandler[]): void;
restoreHandlers(): void;
listHandlers(): ReadonlyArray<RequestHandler>;
events: LifeCycleEventEmitter;
}
function setupServer(...handlers: RequestHandler[]): SetupServerCommonApi;Enhanced Response class with convenience methods for creating various response types with proper headers and content handling.
class HttpResponse<BodyType> extends Response {
constructor(body?: BodyType | null, init?: HttpResponseInit);
static text(body?: string, init?: HttpResponseInit): HttpResponse<string>;
static json<T>(body?: T, init?: HttpResponseInit): HttpResponse<T>;
static xml(body?: string, init?: HttpResponseInit): HttpResponse<string>;
static html(body?: string, init?: HttpResponseInit): HttpResponse<string>;
static arrayBuffer(body?: ArrayBuffer, init?: HttpResponseInit): HttpResponse<ArrayBuffer>;
static formData(body?: FormData, init?: HttpResponseInit): HttpResponse<FormData>;
static error(): HttpResponse<any>;
}
interface HttpResponseInit extends ResponseInit {
type?: ResponseType;
}CLI tool for initializing and managing MSW Service Worker files in browser environments.
# Initialize MSW in project
msw init [publicDir] [--save] [--cwd <directory>]
# Examples:
msw init public/ --save
msw init ./static
msw init --cwd /path/to/projectUtilities for handling request flow, including delays, bypassing MSW interception, and passthrough behavior.
function delay(duration?: number | 'real' | 'infinite'): Promise<void>;
function bypass(input: string | URL | Request, init?: RequestInit): Request;
function passthrough(): HttpResponse<any>;
function getResponse(
handlers: Array<RequestHandler>,
request: Request,
resolutionContext?: ResponseResolutionContext
): Promise<Response | undefined>;// Core handler types
type RequestHandler = HttpHandler | GraphQLHandler | WebSocketHandler;
interface RequestHandlerOptions {
once?: boolean;
}
// HTTP types
interface HttpHandler {
predicate: string | RegExp | HttpCustomPredicate;
resolver: HttpResponseResolver;
options: RequestHandlerOptions;
}
type HttpCustomPredicate = (info: {
request: Request;
parsedResult: HttpRequestParsedResult;
}) => boolean;
interface HttpRequestParsedResult {
match: Match;
query: RequestQuery;
}
type RequestQuery = Record<string, string | string[]>;
// GraphQL types
interface GraphQLHandler {
operationType: 'query' | 'mutation' | 'all';
predicate: string | DocumentNode | GraphQLCustomPredicate;
resolver: GraphQLResponseResolver;
}
type GraphQLCustomPredicate = (info: {
query: any;
variables: Record<string, any>;
operationType: string;
operationName?: string;
}) => boolean;
// WebSocket types
interface WebSocketHandler {
url: string | RegExp;
eventHandlers: Map<string, Function[]>;
}
type WebSocketData = string | ArrayBuffer | Blob;
// Setup types
interface StartOptions {
serviceWorker?: {
url?: string;
options?: RegistrationOptions;
};
quiet?: boolean;
onUnhandledRequest?: 'bypass' | 'warn' | 'error';
}
// Path matching types
type Path = string | RegExp;
type PathParams<T extends string = string> = Record<T, string>;
interface Match {
matches: boolean;
params: Record<string, string>;
}
// Response types
type DefaultBodyType = string | number | boolean | null | undefined | ArrayBuffer | Blob | FormData | ReadableStream;
type JsonBodyType = Record<string, any> | any[];
// Lifecycle types
interface LifeCycleEventsMap {
'request:start': [args: { request: Request; requestId: string }];
'request:match': [args: { request: Request; requestId: string }];
'request:unhandled': [args: { request: Request; requestId: string }];
'request:end': [args: { request: Request; requestId: string }];
'response:mocked': [args: { response: Response; request: Request; requestId: string }];
'response:bypass': [args: { response: Response; request: Request; requestId: string }];
'unhandledException': [args: { error: Error; request: Request; requestId: string }];
}
type LifeCycleEventEmitter = {
on<EventType extends keyof LifeCycleEventsMap>(
event: EventType,
listener: (...args: LifeCycleEventsMap[EventType]) => void
): void;
removeListener<EventType extends keyof LifeCycleEventsMap>(
event: EventType,
listener: (...args: LifeCycleEventsMap[EventType]) => void
): void;
removeAllListeners(event?: keyof LifeCycleEventsMap): void;
};
// Utility types
type DelayMode = 'real' | 'infinite';
type ResponseResolutionContext = {
baseUrl?: string;
};
// Deprecated types (for backward compatibility)
/** @deprecated Use HttpResponse instead */
type StrictResponse<BodyType> = HttpResponse<BodyType>;
// Legacy type aliases
type DefaultRequestMultipartBody = Record<string, string | File>;