Seamless REST/GraphQL API mocking library for browser and Node.js.
—
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Pending
The risk profile of this skill
Browser setup provides Service Worker-based request interception for browser environments with lifecycle control and runtime handler management.
Creates a Service Worker-based request interceptor for browser environments.
/**
* Sets up a Service Worker for intercepting requests in browser environments
* @param handlers - Array of request handlers (HTTP, GraphQL, WebSocket)
* @returns SetupWorker instance with lifecycle control methods
*/
function setupWorker(...handlers: Array<RequestHandler | WebSocketHandler>): SetupWorker;
interface SetupWorker {
/** Start the Service Worker and begin intercepting requests */
start(options?: StartOptions): Promise<ServiceWorkerRegistration | undefined>;
/** Stop the Service Worker and cease request interception */
stop(): void;
/** Add runtime request handlers without restarting */
use(...handlers: Array<RequestHandler | WebSocketHandler>): void;
/** Reset handlers to initial set or provided handlers */
resetHandlers(...handlers?: Array<RequestHandler | WebSocketHandler>): void;
/** Restore handlers to their original state */
restoreHandlers(): void;
/** Get read-only list of currently active handlers */
listHandlers(): ReadonlyArray<RequestHandler | WebSocketHandler>;
/** Event emitter for lifecycle events */
events: LifeCycleEventEmitter<LifeCycleEventsMap>;
}Usage Examples:
import { http, HttpResponse, setupWorker } from "msw";
// Define request handlers
const handlers = [
http.get('/api/user', () => {
return HttpResponse.json({ name: 'John Doe' });
}),
http.post('/api/login', async ({ request }) => {
const { username, password } = await request.json();
return HttpResponse.json({ token: 'fake-token' });
})
];
// Create worker instance
const worker = setupWorker(...handlers);
// Start the worker
async function startMocking() {
try {
await worker.start();
console.log('MSW started successfully');
} catch (error) {
console.error('Failed to start MSW:', error);
}
}
// Call in application bootstrap
startMocking();Start the Service Worker and begin request interception.
/**
* Start the Service Worker and begin intercepting requests
* @param options - Configuration options for worker startup
* @returns Promise resolving to ServiceWorkerRegistration or undefined
*/
start(options?: StartOptions): Promise<ServiceWorkerRegistration | undefined>;
interface StartOptions {
/** Service Worker configuration */
serviceWorker?: {
/** Custom URL for the Service Worker script */
url?: string;
/** Service Worker registration options */
options?: RegistrationOptions;
};
/** Suppress startup logs and warnings */
quiet?: boolean;
/** How to handle requests not matched by any handler */
onUnhandledRequest?: 'bypass' | 'warn' | 'error';
/** Whether to wait for existing Service Worker to update */
waitUntilReady?: boolean;
}Usage Examples:
// Basic startup
await worker.start();
// Custom Service Worker location
await worker.start({
serviceWorker: {
url: '/custom-sw.js'
}
});
// Quiet mode (suppress logs)
await worker.start({
quiet: true
});
// Strict unhandled request handling
await worker.start({
onUnhandledRequest: 'error'
});
// Full configuration
await worker.start({
serviceWorker: {
url: '/mockServiceWorker.js',
options: {
scope: '/api/'
}
},
quiet: false,
onUnhandledRequest: 'warn',
waitUntilReady: true
});
// Handle startup errors
try {
const registration = await worker.start();
if (registration) {
console.log('Service Worker registered:', registration.scope);
}
} catch (error) {
if (error.message.includes('Service Worker')) {
console.warn('Service Worker not supported');
} else {
console.error('Failed to start MSW:', error);
}
}Stop the Service Worker and cease request interception.
/**
* Stop the Service Worker and cease request interception
* Unregisters the Service Worker and cleans up resources
*/
stop(): void;Usage Examples:
// Stop mocking (e.g., in production builds)
worker.stop();
// Conditional stopping
if (process.env.NODE_ENV === 'production') {
worker.stop();
}
// Clean shutdown in application lifecycle
window.addEventListener('beforeunload', () => {
worker.stop();
});Add, reset, and restore request handlers at runtime without restarting the worker.
/**
* Add runtime request handlers without restarting the Service Worker
* @param handlers - Additional handlers to add to current set
*/
use(...handlers: Array<RequestHandler | WebSocketHandler>): void;
/**
* Reset handlers to initial set or provided handlers
* @param handlers - Optional new set of handlers (defaults to initial handlers)
*/
resetHandlers(...handlers?: Array<RequestHandler | WebSocketHandler>): void;
/**
* Restore handlers to their original unmodified state
* Removes any runtime handlers added via use()
*/
restoreHandlers(): void;
/**
* Get read-only list of currently active handlers
* @returns Array of currently active request handlers
*/
listHandlers(): ReadonlyArray<RequestHandler | WebSocketHandler>;Usage Examples:
import { http, HttpResponse } from "msw";
// Add handlers at runtime
worker.use(
http.get('/api/new-endpoint', () => {
return HttpResponse.json({ data: 'new data' });
}),
http.post('/api/temp-endpoint', () => {
return HttpResponse.json({ success: true });
})
);
// Reset to initial handlers
worker.resetHandlers();
// Reset to specific handlers
worker.resetHandlers(
http.get('/api/reset', () => {
return HttpResponse.json({ reset: true });
})
);
// Restore to original state
worker.restoreHandlers();
// Check current handlers
const currentHandlers = worker.listHandlers();
console.log(`Currently ${currentHandlers.length} handlers active`);
// Dynamic handler management based on conditions
function updateHandlersForFeature(enabled: boolean) {
if (enabled) {
worker.use(
http.get('/api/feature', () => {
return HttpResponse.json({ feature: 'enabled' });
})
);
} else {
worker.resetHandlers(); // Remove feature handlers
}
}
// Test-specific handler overrides
function setupTestHandlers() {
worker.use(
http.get('/api/test-data', () => {
return HttpResponse.json({ test: true, data: mockData });
})
);
}
function teardownTestHandlers() {
worker.restoreHandlers();
}Monitor and respond to MSW lifecycle events.
interface LifeCycleEventEmitter<EventsMap> {
/** Add event listener for lifecycle events */
on<EventType extends keyof EventsMap>(
event: EventType,
listener: (...args: EventsMap[EventType]) => void
): void;
/** Remove specific event listener */
removeListener<EventType extends keyof EventsMap>(
event: EventType,
listener: (...args: EventsMap[EventType]) => void
): void;
/** Remove all listeners for event or all events */
removeAllListeners(event?: keyof EventsMap): void;
}
interface LifeCycleEventsMap {
/** Fired when a request starts being processed */
'request:start': [args: { request: Request; requestId: string }];
/** Fired when a request matches a handler */
'request:match': [args: { request: Request; requestId: string }];
/** Fired when a request doesn't match any handler */
'request:unhandled': [args: { request: Request; requestId: string }];
/** Fired when request processing ends */
'request:end': [args: { request: Request; requestId: string }];
/** Fired when a mocked response is sent */
'response:mocked': [args: { response: Response; request: Request; requestId: string }];
/** Fired when a request bypasses MSW */
'response:bypass': [args: { response: Response; request: Request; requestId: string }];
/** Fired when an unhandled exception occurs */
'unhandledException': [args: { error: Error; request: Request; requestId: string }];
}Usage Examples:
// Monitor all requests
worker.events.on('request:start', ({ request, requestId }) => {
console.log('Request started:', request.method, request.url, requestId);
});
// Track matched requests
worker.events.on('request:match', ({ request, requestId }) => {
console.log('Request matched handler:', request.url, requestId);
});
// Handle unmatched requests
worker.events.on('request:unhandled', ({ request, requestId }) => {
console.warn('Unhandled request:', request.method, request.url, requestId);
// Could trigger notifications or logging
if (request.url.includes('/api/')) {
console.error('API request not mocked!');
}
});
// Track mocked responses
worker.events.on('response:mocked', ({ response, request, requestId }) => {
console.log('Mocked response sent:', response.status, request.url, requestId);
});
// Monitor bypassed requests
worker.events.on('response:bypass', ({ response, request, requestId }) => {
console.log('Request bypassed MSW:', request.url, requestId);
});
// Analytics integration
worker.events.on('request:match', ({ request, requestId }) => {
analytics.track('msw_request_mocked', {
method: request.method,
url: request.url,
requestId,
timestamp: Date.now()
});
});
// Development debugging
if (process.env.NODE_ENV === 'development') {
worker.events.on('request:unhandled', ({ request, requestId }) => {
// Show notification for unmocked API calls
showDevNotification(`Unmocked API call: ${request.url} (${requestId})`);
});
}
// Cleanup event listeners
function cleanup() {
worker.events.removeAllListeners();
}Initialize MSW Service Worker file in your application.
CLI Setup:
# Initialize MSW in your public directory
npx msw init public/ --save
# This copies mockServiceWorker.js to public/mockServiceWorker.js
# and optionally saves the path in package.jsonManual Setup:
// In your main application file (e.g., src/main.ts)
async function enableMocking() {
if (process.env.NODE_ENV !== 'development') {
return;
}
const { worker } = await import('./mocks/browser');
// Start the worker
return worker.start({
onUnhandledRequest: 'bypass'
});
}
enableMocking().then(() => {
// Start your React/Vue/Angular app
ReactDOM.render(<App />, document.getElementById('root'));
});Create Mocks File:
// src/mocks/browser.ts
import { setupWorker } from 'msw/browser';
import { handlers } from './handlers';
export const worker = setupWorker(...handlers);// src/mocks/handlers.ts
import { http, HttpResponse } from 'msw';
export const handlers = [
http.get('/api/user', () => {
return HttpResponse.json({
id: 'c7b3d8e0-5e0b-4b0f-8b3a-3b9f4b3d3b3d',
firstName: 'John',
lastName: 'Maverick',
});
}),
];Handle browser-specific scenarios and limitations.
// Feature detection
async function startMockingIfSupported() {
if (!('serviceWorker' in navigator)) {
console.warn('Service Worker not supported');
return;
}
try {
await worker.start();
} catch (error) {
console.warn('Could not start Service Worker:', error);
}
}
// HTTPS requirement handling
if (location.protocol === 'https:' || location.hostname === 'localhost') {
worker.start({
quiet: process.env.NODE_ENV === 'production'
});
} else {
console.warn('MSW requires HTTPS or localhost');
}
// Development vs production
if (process.env.NODE_ENV === 'development') {
worker.start({
onUnhandledRequest: 'warn'
});
} else if (process.env.NODE_ENV === 'test') {
worker.start({
quiet: true,
onUnhandledRequest: 'error'
});
}
// Hot module replacement support
if (module.hot) {
module.hot.accept('./handlers', () => {
worker.resetHandlers();
});
}// Setup types
interface SetupWorker {
start(options?: StartOptions): Promise<ServiceWorkerRegistration | undefined>;
stop(): void;
use(...handlers: Array<RequestHandler | WebSocketHandler>): void;
resetHandlers(...handlers?: Array<RequestHandler | WebSocketHandler>): void;
restoreHandlers(): void;
listHandlers(): ReadonlyArray<RequestHandler | WebSocketHandler>;
events: LifeCycleEventEmitter<LifeCycleEventsMap>;
}
// Configuration types
interface StartOptions {
serviceWorker?: {
url?: string;
options?: RegistrationOptions;
};
quiet?: boolean;
onUnhandledRequest?: 'bypass' | 'warn' | 'error';
waitUntilReady?: boolean;
}
// Service Worker types
interface RegistrationOptions {
scope?: string;
type?: WorkerType;
updateViaCache?: ServiceWorkerUpdateViaCache;
}
// Handler types (re-exported from core)
type RequestHandler = HttpHandler | GraphQLHandler | WebSocketHandler;
type WebSocketHandler = import('../core/handlers/WebSocketHandler').WebSocketHandler;