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
Node.js setup provides low-level request interception for Node.js environments using HTTP/HTTPS module patching, ideal for testing scenarios and server-side mocking.
Creates a low-level request interceptor for Node.js environments.
/**
* Sets up request interception for Node.js environments
* @param handlers - Array of request handlers (HTTP, GraphQL, WebSocket)
* @returns SetupServer instance with lifecycle control methods
*/
function setupServer(...handlers: Array<RequestHandler | WebSocketHandler>): SetupServer;
interface SetupServer {
/** Start intercepting requests */
listen(options?: ListenOptions): void;
/** Stop intercepting requests and restore original behavior */
close(): 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, setupServer } from "msw/node";
// Define request handlers
const handlers = [
http.get('https://api.example.com/user', () => {
return HttpResponse.json({ name: 'John Doe' });
}),
http.post('https://api.example.com/login', async ({ request }) => {
const { username, password } = await request.json();
return HttpResponse.json({ token: 'fake-token' });
})
];
// Create server instance
const server = setupServer(...handlers);
// Start interception (typically in test setup)
server.listen();
// Stop interception (typically in test teardown)
server.close();Start intercepting requests with optional configuration.
/**
* Start intercepting requests
* @param options - Configuration options for request interception
*/
listen(options?: ListenOptions): void;
interface ListenOptions {
/** How to handle requests not matched by any handler */
onUnhandledRequest?: 'bypass' | 'warn' | 'error' | ((request: Request, print: { warning(): void; error(): void }) => void);
}Usage Examples:
// Basic setup - start intercepting
server.listen();
// Warn about unhandled requests
server.listen({
onUnhandledRequest: 'warn'
});
// Error on unhandled requests (strict mode)
server.listen({
onUnhandledRequest: 'error'
});
// Custom unhandled request handling
server.listen({
onUnhandledRequest(request, print) {
// Custom logic for unhandled requests
if (request.url.includes('/api/')) {
print.error();
} else {
print.warning();
}
}
});
// Bypass unhandled requests silently (default)
server.listen({
onUnhandledRequest: 'bypass'
});Stop intercepting requests and restore original HTTP behavior.
/**
* Stop intercepting requests and restore original behavior
* Restores Node.js built-in HTTP/HTTPS modules to their original state
*/
close(): void;Usage Examples:
// Stop interception
server.close();
// In test lifecycle
afterAll(() => {
server.close();
});
// Conditional cleanup
if (process.env.NODE_ENV === 'test') {
process.on('exit', () => {
server.close();
});
}Add, reset, and restore request handlers at runtime.
/**
* Add runtime request handlers without restarting
* @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:
// Add handlers for specific test
beforeEach(() => {
server.use(
http.get('https://api.example.com/test-data', () => {
return HttpResponse.json({ test: true });
})
);
});
// Reset to original handlers after each test
afterEach(() => {
server.resetHandlers();
});
// Override specific handler for one test
test('should handle error scenario', async () => {
server.use(
http.get('https://api.example.com/user', () => {
return HttpResponse.json(
{ error: 'User not found' },
{ status: 404 }
);
})
);
// Test error handling...
});
// Restore to completely clean state
afterAll(() => {
server.restoreHandlers();
});
// Check current handlers count
const handlerCount = server.listHandlers().length;
console.log(`Currently ${handlerCount} handlers active`);Common patterns for integrating MSW with testing frameworks.
Jest Integration:
// tests/setup.ts
import { beforeAll, afterEach, afterAll } from '@jest/globals';
import { setupServer } from 'msw/node';
import { handlers } from '../src/mocks/handlers';
const server = setupServer(...handlers);
// Start server before all tests
beforeAll(() => {
server.listen({
onUnhandledRequest: 'error'
});
});
// Reset handlers after each test
afterEach(() => {
server.resetHandlers();
});
// Clean up after all tests
afterAll(() => {
server.close();
});
export { server };Vitest Integration:
// vitest.setup.ts
import { beforeAll, afterEach, afterAll } from 'vitest';
import { setupServer } from 'msw/node';
import { handlers } from './src/mocks/handlers';
const server = setupServer(...handlers);
beforeAll(() => {
server.listen({ onUnhandledRequest: 'error' });
});
afterEach(() => {
server.resetHandlers();
});
afterAll(() => {
server.close();
});Test-Specific Handlers:
import { server } from './setup';
import { http, HttpResponse } from 'msw';
describe('User API', () => {
test('handles server error', async () => {
// Override handler for this test
server.use(
http.get('https://api.example.com/user', () => {
return HttpResponse.json(
{ error: 'Internal server error' },
{ status: 500 }
);
})
);
// Your test code that expects the error...
});
test('handles network error', async () => {
server.use(
http.get('https://api.example.com/user', () => {
return HttpResponse.error();
})
);
// Your test code that expects network error...
});
});Handle different Node.js environments and configurations.
// Development server setup
if (process.env.NODE_ENV === 'development') {
const { setupServer } = require('msw/node');
const { handlers } = require('./mocks/handlers');
const server = setupServer(...handlers);
server.listen({
onUnhandledRequest: 'warn'
});
console.log('Mock server running');
}
// Test environment setup
if (process.env.NODE_ENV === 'test') {
global.mockServer = setupServer();
// Automatically clean up on process exit
process.on('beforeExit', () => {
global.mockServer.close();
});
}
// CI environment setup
const server = setupServer(...handlers);
if (process.env.CI) {
server.listen({
onUnhandledRequest: 'error' // Strict mode in CI
});
} else {
server.listen({
onUnhandledRequest: 'warn' // More lenient in local dev
});
}Monitor request interception and handler execution.
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:
// Log all intercepted requests
server.events.on('request:start', ({ request, requestId }) => {
console.log(`Intercepting: ${request.method} ${request.url} (${requestId})`);
});
// Track unhandled requests for debugging
server.events.on('request:unhandled', ({ request, requestId }) => {
console.warn(`Unhandled request: ${request.method} ${request.url} (${requestId})`);
// Could help identify missing mocks
if (request.url.includes('/api/')) {
console.error('Missing API mock for:', request.url);
}
});
// Performance monitoring
server.events.on('response:mocked', ({ response, request, requestId }) => {
console.log(`Mocked response (${response.status}) for ${request.url} (${requestId})`);
});
// Test debugging
if (process.env.NODE_ENV === 'test') {
server.events.on('request:match', ({ request, requestId }) => {
console.debug(`Test mock matched: ${request.url} (${requestId})`);
});
}
// Request analytics
const requestStats = {
total: 0,
mocked: 0,
bypassed: 0
};
server.events.on('request:start', () => {
requestStats.total++;
});
server.events.on('response:mocked', () => {
requestStats.mocked++;
});
server.events.on('response:bypass', () => {
requestStats.bypassed++;
});
// Cleanup listeners
afterAll(() => {
server.events.removeAllListeners();
});Handle complex Node.js scenarios and integrations.
// Dynamic handler loading based on environment
async function setupDynamicServer() {
const baseHandlers = await import('./mocks/base-handlers');
let environmentHandlers = [];
if (process.env.NODE_ENV === 'test') {
environmentHandlers = await import('./mocks/test-handlers');
} else if (process.env.NODE_ENV === 'development') {
environmentHandlers = await import('./mocks/dev-handlers');
}
const server = setupServer(
...baseHandlers.default,
...environmentHandlers.default
);
return server;
}
// Configuration-driven handler setup
interface MockConfig {
enabled: boolean;
handlers: string[];
strictMode: boolean;
}
async function setupConfiguredServer(config: MockConfig) {
if (!config.enabled) {
return null;
}
const handlers = await Promise.all(
config.handlers.map(path => import(path))
);
const server = setupServer(
...handlers.flatMap(module => module.default)
);
server.listen({
onUnhandledRequest: config.strictMode ? 'error' : 'warn'
});
return server;
}
// Express.js integration
import express from 'express';
const app = express();
const server = setupServer(...handlers);
// Start MSW before starting Express server
server.listen();
app.listen(3000, () => {
console.log('Server running with MSW interception');
});
// Graceful shutdown
process.on('SIGTERM', () => {
server.close();
process.exit(0);
});// Setup types
interface SetupServer {
listen(options?: ListenOptions): void;
close(): 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 ListenOptions {
onUnhandledRequest?:
| 'bypass'
| 'warn'
| 'error'
| ((request: Request, print: { warning(): void; error(): void }) => void);
}
// Handler types (re-exported from core)
type RequestHandler = HttpHandler | GraphQLHandler | WebSocketHandler;
// Event types (same as browser but specific to Node.js context)
interface LifeCycleEventsMap {
'request:start': [request: Request];
'request:match': [request: Request];
'request:unhandled': [request: Request];
'response:mocked': [response: Response, requestId: string];
'response:bypass': [response: Response, requestId: string];
}