CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-msw

Seamless REST/GraphQL API mocking library for browser and Node.js.

Pending
Quality

Pending

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

SecuritybySnyk

Pending

The risk profile of this skill

Overview
Eval results
Files

utilities.mddocs/

Request Utilities

Request utilities provide helper functions for handling request flow, including delays, bypassing MSW interception, and passthrough behavior.

Capabilities

Delay Function

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 });
});

Bypass Function

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();
}

Passthrough Function

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' });
  }
});

Response Resolution

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 }

URL Matching Utilities

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);
});

Asset Request Detection

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');
});

Advanced Utility Patterns

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()
    });
  }
});

Types

// 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;

docs

browser-setup.md

cli.md

graphql-handlers.md

http-handlers.md

index.md

nodejs-setup.md

react-native-setup.md

response-creation.md

utilities.md

websocket-handlers.md

tile.json