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
Request utilities provide helper functions for handling request flow, including delays, bypassing MSW interception, and passthrough behavior.
Add realistic response delays to simulate network latency and server processing time.
/**
* Delays the response by the given duration or mode
* @param durationOrMode - Delay duration in milliseconds, or delay mode ('real', 'infinite')
* @returns Promise that resolves after the specified delay
*/
function delay(durationOrMode?: DelayMode | number): Promise<void>;
type DelayMode = 'real' | 'infinite';Usage Examples:
import { http, HttpResponse, delay } from "msw";
// Default realistic delay (100-400ms in browser, 5ms in Node.js)
http.get('/api/user', async () => {
await delay(); // Random realistic delay
return HttpResponse.json({ name: 'John Doe' });
});
// Specific delay duration
http.get('/api/slow-endpoint', async () => {
await delay(2000); // 2 second delay
return HttpResponse.json({ data: 'slow data' });
});
// Realistic delay mode (same as default)
http.get('/api/realistic', async () => {
await delay('real'); // Random 100-400ms (browser) or 5ms (Node.js)
return HttpResponse.json({ data: 'realistic timing' });
});
// Infinite delay (useful for testing timeouts)
http.get('/api/timeout-test', async () => {
await delay('infinite'); // Never resolves
return HttpResponse.json({ data: 'never returned' });
});
// Conditional delays
http.get('/api/conditional-delay', async ({ request }) => {
const url = new URL(request.url);
const slow = url.searchParams.has('slow');
if (slow) {
await delay(3000); // Slow mode
} else {
await delay(100); // Fast mode
}
return HttpResponse.json({ mode: slow ? 'slow' : 'fast' });
});
// Simulating different network conditions
http.get('/api/network-simulation', async ({ request }) => {
const connection = request.headers.get('x-connection-type');
switch (connection) {
case 'slow-3g':
await delay(1500);
break;
case 'fast-3g':
await delay(800);
break;
case '4g':
await delay(200);
break;
case 'wifi':
await delay(50);
break;
default:
await delay(); // Default realistic delay
}
return HttpResponse.json({
data: 'network-dependent response',
connection
});
});
// Progressive delays for retry testing
let attemptCount = 0;
http.get('/api/retry-test', async () => {
attemptCount++;
// Exponential backoff simulation
const delayMs = Math.min(1000 * Math.pow(2, attemptCount - 1), 10000);
await delay(delayMs);
if (attemptCount < 3) {
return HttpResponse.json(
{ error: 'Temporary failure' },
{ status: 503 }
);
}
attemptCount = 0; // Reset for next test
return HttpResponse.json({ success: true, attempts: 3 });
});Create requests that bypass MSW interception entirely, allowing real network requests.
/**
* Creates a Request instance that will always be ignored by MSW
* @param input - URL string, URL object, or Request instance to bypass
* @param init - Optional RequestInit for additional request configuration
* @returns Request instance marked to bypass MSW interception
*/
function bypass(input: BypassRequestInput, init?: RequestInit): Request;
type BypassRequestInput = string | URL | Request;Usage Examples:
import { bypass } from "msw";
// Bypass MSW for specific request
async function fetchRealData() {
const response = await fetch(bypass('https://api.real-service.com/data'));
return response.json();
}
// Bypass with URL object
async function fetchWithURL() {
const url = new URL('https://api.example.com/real');
url.searchParams.set('param', 'value');
const response = await fetch(bypass(url));
return response.json();
}
// Bypass with Request object
async function fetchWithRequest() {
const request = new Request('https://api.example.com/data', {
method: 'POST',
body: JSON.stringify({ test: true })
});
const response = await fetch(bypass(request));
return response.json();
}
// Bypass with additional init options
async function fetchWithInit() {
const response = await fetch(
bypass('https://api.example.com/data'),
{
headers: {
'Authorization': 'Bearer real-token'
}
}
);
return response.json();
}
// Conditional bypass based on environment
async function fetchConditionally(url: string) {
const request = process.env.NODE_ENV === 'production'
? bypass(url) // Use real API in production
: url; // Use MSW in development/testing
const response = await fetch(request);
return response.json();
}
// Mixing real and mocked requests
http.get('/api/mixed-data', async () => {
// Fetch some real data alongside mocked response
const realData = await fetch(bypass('https://api.external.com/status'))
.then(res => res.json())
.catch(() => ({ status: 'unavailable' }));
return HttpResponse.json({
mockedData: { message: 'This is mocked' },
realData: realData
});
});
// Testing real API integration
describe('Real API Integration', () => {
test('should work with real API', async () => {
// Bypass MSW for this specific test
const response = await fetch(bypass('https://jsonplaceholder.typicode.com/posts/1'));
const data = await response.json();
expect(data).toHaveProperty('id', 1);
});
});
// Graceful fallback pattern
async function fetchWithFallback(url: string) {
try {
// Try real API first
const response = await fetch(bypass(url));
if (response.ok) {
return response.json();
}
} catch (error) {
console.warn('Real API failed, using fallback');
}
// Fallback to mocked data (regular fetch will be intercepted by MSW)
const fallbackResponse = await fetch(url);
return fallbackResponse.json();
}Allow intercepted requests to pass through without mocking, continuing to their original destination.
/**
* Performs the intercepted request as-is without modification
* Stops request handler lookup and allows request to proceed normally
* @returns HttpResponse indicating passthrough behavior
*/
function passthrough(): HttpResponse<any>;Usage Examples:
import { http, passthrough } from "msw";
// Conditional passthrough based on request properties
http.get('/api/*', ({ request }) => {
const url = new URL(request.url);
// Pass through requests with specific header
if (request.headers.get('x-passthrough') === 'true') {
return passthrough();
}
// Mock other requests
return HttpResponse.json({ mocked: true });
});
// Passthrough for specific environments
http.all('*', ({ request }) => {
// In production, pass through all requests to real API
if (process.env.NODE_ENV === 'production') {
return passthrough();
}
// In development/test, continue with other handlers
return HttpResponse.json({ environment: 'development' });
});
// Passthrough based on authentication
http.get('/api/protected/*', ({ request }) => {
const authHeader = request.headers.get('authorization');
// If real auth token present, pass through to real API
if (authHeader?.startsWith('Bearer real_')) {
return passthrough();
}
// Mock for test tokens
return HttpResponse.json({
data: 'mocked protected data',
user: 'test-user'
});
});
// Selective mocking with passthrough fallback
http.get('/api/users/:id', ({ params }) => {
const userId = params.id;
// Mock specific test users
if (userId === '1' || userId === '2') {
return HttpResponse.json({
id: userId,
name: `Test User ${userId}`,
email: `test${userId}@example.com`
});
}
// Pass through for other user IDs
return passthrough();
});
// Development mode with passthrough option
http.get('/api/feature-flag', ({ request }) => {
const url = new URL(request.url);
const useReal = url.searchParams.get('real') === 'true';
if (useReal) {
console.log('Using real feature flag service');
return passthrough();
}
// Mock feature flags for development
return HttpResponse.json({
featureA: true,
featureB: false,
featureC: Math.random() > 0.5
});
});
// Hybrid testing approach
http.post('/api/analytics', ({ request }) => {
// Always passthrough analytics in test environment
// to avoid affecting real metrics while still testing integration
if (process.env.NODE_ENV === 'test') {
console.log('Analytics call passed through');
return passthrough();
}
// Mock in development to avoid sending test data
return HttpResponse.json({ tracked: true });
});
// Geographic routing
http.get('/api/content', ({ request }) => {
const geo = request.headers.get('x-geo-country');
// Pass through for specific regions that need real API
if (geo === 'US' || geo === 'CA') {
return passthrough();
}
// Mock for other regions
return HttpResponse.json({
content: 'localized mock content',
region: geo || 'unknown'
});
});
// Performance testing scenario
http.get('/api/performance-test', ({ request }) => {
const testMode = request.headers.get('x-test-mode');
switch (testMode) {
case 'real-api':
// Test against real API for performance baseline
return passthrough();
case 'mock-fast':
// Fast mock response for speed testing
return HttpResponse.json({ data: 'fast mock' });
case 'mock-slow':
// Slow mock response for timeout testing
return delay(5000).then(() =>
HttpResponse.json({ data: 'slow mock' })
);
default:
return HttpResponse.json({ data: 'default mock' });
}
});Programmatically resolve requests against handlers for advanced use cases and custom request handling logic.
/**
* Finds and executes the first matching request handler for a given request
* @param handlers - Array of MSW request handlers to test against the request
* @param request - The incoming HTTP request (standard Fetch API Request object)
* @param resolutionContext - Optional configuration object with baseUrl for URL matching
* @returns Promise resolving to Response object if a handler matches, undefined otherwise
*/
function getResponse(
handlers: Array<RequestHandler>,
request: Request,
resolutionContext?: ResponseResolutionContext
): Promise<Response | undefined>;
interface ResponseResolutionContext {
baseUrl?: string;
}Usage Examples:
import { getResponse, http, HttpResponse } from "msw";
// Basic usage - resolve request against handlers
const handlers = [
http.get('/api/user', () => HttpResponse.json({ name: 'John' })),
http.post('/api/user', () => new Response('Created', { status: 201 }))
];
const request = new Request('https://example.com/api/user');
const response = await getResponse(handlers, request);
// Returns: Response with JSON { name: 'John' }
// Advanced: Batched GraphQL operations
function batchedGraphQLQuery(url: string, handlers: Array<RequestHandler>) {
return http.post(url, async ({ request }) => {
const operations = await request.json();
const responses = await Promise.all(
operations.map(async (operation) => {
const scopedRequest = new Request(request, {
body: JSON.stringify(operation),
});
const response = await getResponse(handlers, scopedRequest);
return response || fetch(bypass(scopedRequest));
})
);
return HttpResponse.json(responses.map(r => r.json()));
});
}
// Custom handler composition
const compositeHandler = http.get('/api/data', async ({ request }) => {
// Try specialized handlers first
const specializedResponse = await getResponse(specializedHandlers, request);
if (specializedResponse) {
return specializedResponse;
}
// Fall back to default behavior
return HttpResponse.json({ message: 'Default response' });
});
// Testing utility - programmatically test handler behavior
const testHandlers = [
http.get('/test', () => HttpResponse.json({ test: true }))
];
const testRequest = new Request('http://localhost/test');
const testResponse = await getResponse(testHandlers, testRequest);
console.log(await testResponse?.json()); // { test: true }Utilities for working with URL patterns and matching.
/**
* Match request URL against a predicate pattern
* @param url - URL to match against
* @param predicate - String pattern, RegExp, or Path to match
* @returns Match result with success status and extracted parameters
*/
function matchRequestUrl(url: URL, predicate: Path): Match;
/**
* Clean and normalize URL for consistent matching
* @param url - URL string to clean
* @returns Cleaned URL string
*/
function cleanUrl(url: string): string;
type Path = string | RegExp;
interface Match<Params = Record<string, string>> {
matches: boolean;
params: Params;
}Usage Examples:
import { matchRequestUrl, cleanUrl } from "msw";
// Custom request matching logic
function customMatcher(request: Request): boolean {
const url = new URL(request.url);
const match = matchRequestUrl(url, '/api/users/:id');
if (match.matches) {
console.log('Matched user ID:', match.params.id);
return true;
}
return false;
}
// URL cleaning for consistent matching
function normalizeUrl(url: string): string {
const cleaned = cleanUrl(url);
console.log('Original:', url);
console.log('Cleaned:', cleaned);
return cleaned;
}
// Pattern matching examples
const testCases = [
{ url: 'https://api.example.com/users/123', pattern: '/users/:id' },
{ url: 'https://api.example.com/posts?page=1', pattern: '/posts' },
{ url: 'https://api.example.com/files/doc.pdf', pattern: /\/files\/.+\.pdf$/ }
];
testCases.forEach(({ url, pattern }) => {
const match = matchRequestUrl(new URL(url), pattern);
console.log(`${url} matches ${pattern}:`, match);
});Utility to identify common asset requests that typically shouldn't be mocked.
/**
* Check if request is for a common static asset
* @param request - Request object to check
* @returns True if request appears to be for a static asset
*/
function isCommonAssetRequest(request: Request): boolean;Usage Examples:
import { http, HttpResponse, isCommonAssetRequest, passthrough } from "msw";
// Skip mocking for asset requests
http.all('*', ({ request }) => {
// Let asset requests pass through to real server
if (isCommonAssetRequest(request)) {
return passthrough();
}
// Continue with API mocking for non-asset requests
return HttpResponse.json({ intercepted: true });
});
// Conditional handling based on request type
http.get('*', ({ request }) => {
const url = new URL(request.url);
if (isCommonAssetRequest(request)) {
console.log('Asset request detected:', url.pathname);
return passthrough();
}
if (url.pathname.startsWith('/api/')) {
return HttpResponse.json({ api: 'mocked' });
}
// Default fallback
return passthrough();
});
// Logging asset vs API requests
http.all('*', ({ request }) => {
const isAsset = isCommonAssetRequest(request);
console.log(`Request type: ${isAsset ? 'Asset' : 'API'} - ${request.url}`);
if (isAsset) {
return passthrough();
}
return HttpResponse.text('Caught by MSW');
});Combine utilities for sophisticated request handling scenarios.
// Smart passthrough with delay simulation
http.get('/api/smart-proxy', async ({ request }) => {
const url = new URL(request.url);
const simulateDelay = url.searchParams.get('delay');
if (simulateDelay) {
await delay(parseInt(simulateDelay));
}
// Pass through to real API after delay
return passthrough();
});
// Conditional bypass based on request content
http.post('/api/conditional-bypass', async ({ request }) => {
const body = await request.json();
// Bypass for real data, mock for test data
if (body.useRealApi === true) {
// Create new request with bypass
return fetch(bypass(request.url, {
method: request.method,
body: JSON.stringify(body),
headers: request.headers
}));
}
await delay(100);
return HttpResponse.json({
mocked: true,
received: body
});
});
// Hybrid real/mock response
http.get('/api/hybrid-data', async ({ request }) => {
try {
// Try to get real data
const realResponse = await fetch(bypass(request.url));
const realData = await realResponse.json();
// Combine with mock data
await delay(50); // Add some realistic delay
return HttpResponse.json({
real: realData,
mock: { timestamp: Date.now(), generated: true }
});
} catch (error) {
// Fall back to pure mock if real API fails
await delay(100);
return HttpResponse.json({
real: null,
mock: { error: 'Real API unavailable', fallback: true },
timestamp: Date.now()
});
}
});// Delay types
type DelayMode = 'real' | 'infinite';
// Bypass types
type BypassRequestInput = string | URL | Request;
// URL matching types
type Path = string | RegExp;
type PathParams<T extends string = string> = Record<T, string>;
interface Match<Params = Record<string, string>> {
matches: boolean;
params: Params;
}
// Utility function signatures
declare function delay(durationOrMode?: DelayMode | number): Promise<void>;
declare function bypass(input: BypassRequestInput, init?: RequestInit): Request;
declare function passthrough(): HttpResponse<any>;
declare function matchRequestUrl(url: URL, predicate: Path): Match;
declare function cleanUrl(url: string): string;
declare function isCommonAssetRequest(request: Request): boolean;
// Constants
declare const SET_TIMEOUT_MAX_ALLOWED_INT: number;
declare const MIN_SERVER_RESPONSE_TIME: number;
declare const MAX_SERVER_RESPONSE_TIME: number;
declare const NODE_SERVER_RESPONSE_TIME: number;