0
# Request Utilities
1
2
Request utilities provide helper functions for handling request flow, including delays, bypassing MSW interception, and passthrough behavior.
3
4
## Capabilities
5
6
### Delay Function
7
8
Add realistic response delays to simulate network latency and server processing time.
9
10
```typescript { .api }
11
/**
12
* Delays the response by the given duration or mode
13
* @param durationOrMode - Delay duration in milliseconds, or delay mode ('real', 'infinite')
14
* @returns Promise that resolves after the specified delay
15
*/
16
function delay(durationOrMode?: DelayMode | number): Promise<void>;
17
18
type DelayMode = 'real' | 'infinite';
19
```
20
21
**Usage Examples:**
22
23
```typescript
24
import { http, HttpResponse, delay } from "msw";
25
26
// Default realistic delay (100-400ms in browser, 5ms in Node.js)
27
http.get('/api/user', async () => {
28
await delay(); // Random realistic delay
29
return HttpResponse.json({ name: 'John Doe' });
30
});
31
32
// Specific delay duration
33
http.get('/api/slow-endpoint', async () => {
34
await delay(2000); // 2 second delay
35
return HttpResponse.json({ data: 'slow data' });
36
});
37
38
// Realistic delay mode (same as default)
39
http.get('/api/realistic', async () => {
40
await delay('real'); // Random 100-400ms (browser) or 5ms (Node.js)
41
return HttpResponse.json({ data: 'realistic timing' });
42
});
43
44
// Infinite delay (useful for testing timeouts)
45
http.get('/api/timeout-test', async () => {
46
await delay('infinite'); // Never resolves
47
return HttpResponse.json({ data: 'never returned' });
48
});
49
50
// Conditional delays
51
http.get('/api/conditional-delay', async ({ request }) => {
52
const url = new URL(request.url);
53
const slow = url.searchParams.has('slow');
54
55
if (slow) {
56
await delay(3000); // Slow mode
57
} else {
58
await delay(100); // Fast mode
59
}
60
61
return HttpResponse.json({ mode: slow ? 'slow' : 'fast' });
62
});
63
64
// Simulating different network conditions
65
http.get('/api/network-simulation', async ({ request }) => {
66
const connection = request.headers.get('x-connection-type');
67
68
switch (connection) {
69
case 'slow-3g':
70
await delay(1500);
71
break;
72
case 'fast-3g':
73
await delay(800);
74
break;
75
case '4g':
76
await delay(200);
77
break;
78
case 'wifi':
79
await delay(50);
80
break;
81
default:
82
await delay(); // Default realistic delay
83
}
84
85
return HttpResponse.json({
86
data: 'network-dependent response',
87
connection
88
});
89
});
90
91
// Progressive delays for retry testing
92
let attemptCount = 0;
93
http.get('/api/retry-test', async () => {
94
attemptCount++;
95
96
// Exponential backoff simulation
97
const delayMs = Math.min(1000 * Math.pow(2, attemptCount - 1), 10000);
98
await delay(delayMs);
99
100
if (attemptCount < 3) {
101
return HttpResponse.json(
102
{ error: 'Temporary failure' },
103
{ status: 503 }
104
);
105
}
106
107
attemptCount = 0; // Reset for next test
108
return HttpResponse.json({ success: true, attempts: 3 });
109
});
110
```
111
112
### Bypass Function
113
114
Create requests that bypass MSW interception entirely, allowing real network requests.
115
116
```typescript { .api }
117
/**
118
* Creates a Request instance that will always be ignored by MSW
119
* @param input - URL string, URL object, or Request instance to bypass
120
* @param init - Optional RequestInit for additional request configuration
121
* @returns Request instance marked to bypass MSW interception
122
*/
123
function bypass(input: BypassRequestInput, init?: RequestInit): Request;
124
125
type BypassRequestInput = string | URL | Request;
126
```
127
128
**Usage Examples:**
129
130
```typescript
131
import { bypass } from "msw";
132
133
// Bypass MSW for specific request
134
async function fetchRealData() {
135
const response = await fetch(bypass('https://api.real-service.com/data'));
136
return response.json();
137
}
138
139
// Bypass with URL object
140
async function fetchWithURL() {
141
const url = new URL('https://api.example.com/real');
142
url.searchParams.set('param', 'value');
143
144
const response = await fetch(bypass(url));
145
return response.json();
146
}
147
148
// Bypass with Request object
149
async function fetchWithRequest() {
150
const request = new Request('https://api.example.com/data', {
151
method: 'POST',
152
body: JSON.stringify({ test: true })
153
});
154
155
const response = await fetch(bypass(request));
156
return response.json();
157
}
158
159
// Bypass with additional init options
160
async function fetchWithInit() {
161
const response = await fetch(
162
bypass('https://api.example.com/data'),
163
{
164
headers: {
165
'Authorization': 'Bearer real-token'
166
}
167
}
168
);
169
return response.json();
170
}
171
172
// Conditional bypass based on environment
173
async function fetchConditionally(url: string) {
174
const request = process.env.NODE_ENV === 'production'
175
? bypass(url) // Use real API in production
176
: url; // Use MSW in development/testing
177
178
const response = await fetch(request);
179
return response.json();
180
}
181
182
// Mixing real and mocked requests
183
http.get('/api/mixed-data', async () => {
184
// Fetch some real data alongside mocked response
185
const realData = await fetch(bypass('https://api.external.com/status'))
186
.then(res => res.json())
187
.catch(() => ({ status: 'unavailable' }));
188
189
return HttpResponse.json({
190
mockedData: { message: 'This is mocked' },
191
realData: realData
192
});
193
});
194
195
// Testing real API integration
196
describe('Real API Integration', () => {
197
test('should work with real API', async () => {
198
// Bypass MSW for this specific test
199
const response = await fetch(bypass('https://jsonplaceholder.typicode.com/posts/1'));
200
const data = await response.json();
201
202
expect(data).toHaveProperty('id', 1);
203
});
204
});
205
206
// Graceful fallback pattern
207
async function fetchWithFallback(url: string) {
208
try {
209
// Try real API first
210
const response = await fetch(bypass(url));
211
if (response.ok) {
212
return response.json();
213
}
214
} catch (error) {
215
console.warn('Real API failed, using fallback');
216
}
217
218
// Fallback to mocked data (regular fetch will be intercepted by MSW)
219
const fallbackResponse = await fetch(url);
220
return fallbackResponse.json();
221
}
222
```
223
224
### Passthrough Function
225
226
Allow intercepted requests to pass through without mocking, continuing to their original destination.
227
228
```typescript { .api }
229
/**
230
* Performs the intercepted request as-is without modification
231
* Stops request handler lookup and allows request to proceed normally
232
* @returns HttpResponse indicating passthrough behavior
233
*/
234
function passthrough(): HttpResponse<any>;
235
```
236
237
**Usage Examples:**
238
239
```typescript
240
import { http, passthrough } from "msw";
241
242
// Conditional passthrough based on request properties
243
http.get('/api/*', ({ request }) => {
244
const url = new URL(request.url);
245
246
// Pass through requests with specific header
247
if (request.headers.get('x-passthrough') === 'true') {
248
return passthrough();
249
}
250
251
// Mock other requests
252
return HttpResponse.json({ mocked: true });
253
});
254
255
// Passthrough for specific environments
256
http.all('*', ({ request }) => {
257
// In production, pass through all requests to real API
258
if (process.env.NODE_ENV === 'production') {
259
return passthrough();
260
}
261
262
// In development/test, continue with other handlers
263
return HttpResponse.json({ environment: 'development' });
264
});
265
266
// Passthrough based on authentication
267
http.get('/api/protected/*', ({ request }) => {
268
const authHeader = request.headers.get('authorization');
269
270
// If real auth token present, pass through to real API
271
if (authHeader?.startsWith('Bearer real_')) {
272
return passthrough();
273
}
274
275
// Mock for test tokens
276
return HttpResponse.json({
277
data: 'mocked protected data',
278
user: 'test-user'
279
});
280
});
281
282
// Selective mocking with passthrough fallback
283
http.get('/api/users/:id', ({ params }) => {
284
const userId = params.id;
285
286
// Mock specific test users
287
if (userId === '1' || userId === '2') {
288
return HttpResponse.json({
289
id: userId,
290
name: `Test User ${userId}`,
291
email: `test${userId}@example.com`
292
});
293
}
294
295
// Pass through for other user IDs
296
return passthrough();
297
});
298
299
// Development mode with passthrough option
300
http.get('/api/feature-flag', ({ request }) => {
301
const url = new URL(request.url);
302
const useReal = url.searchParams.get('real') === 'true';
303
304
if (useReal) {
305
console.log('Using real feature flag service');
306
return passthrough();
307
}
308
309
// Mock feature flags for development
310
return HttpResponse.json({
311
featureA: true,
312
featureB: false,
313
featureC: Math.random() > 0.5
314
});
315
});
316
317
// Hybrid testing approach
318
http.post('/api/analytics', ({ request }) => {
319
// Always passthrough analytics in test environment
320
// to avoid affecting real metrics while still testing integration
321
if (process.env.NODE_ENV === 'test') {
322
console.log('Analytics call passed through');
323
return passthrough();
324
}
325
326
// Mock in development to avoid sending test data
327
return HttpResponse.json({ tracked: true });
328
});
329
330
// Geographic routing
331
http.get('/api/content', ({ request }) => {
332
const geo = request.headers.get('x-geo-country');
333
334
// Pass through for specific regions that need real API
335
if (geo === 'US' || geo === 'CA') {
336
return passthrough();
337
}
338
339
// Mock for other regions
340
return HttpResponse.json({
341
content: 'localized mock content',
342
region: geo || 'unknown'
343
});
344
});
345
346
// Performance testing scenario
347
http.get('/api/performance-test', ({ request }) => {
348
const testMode = request.headers.get('x-test-mode');
349
350
switch (testMode) {
351
case 'real-api':
352
// Test against real API for performance baseline
353
return passthrough();
354
355
case 'mock-fast':
356
// Fast mock response for speed testing
357
return HttpResponse.json({ data: 'fast mock' });
358
359
case 'mock-slow':
360
// Slow mock response for timeout testing
361
return delay(5000).then(() =>
362
HttpResponse.json({ data: 'slow mock' })
363
);
364
365
default:
366
return HttpResponse.json({ data: 'default mock' });
367
}
368
});
369
```
370
371
### Response Resolution
372
373
Programmatically resolve requests against handlers for advanced use cases and custom request handling logic.
374
375
```typescript { .api }
376
/**
377
* Finds and executes the first matching request handler for a given request
378
* @param handlers - Array of MSW request handlers to test against the request
379
* @param request - The incoming HTTP request (standard Fetch API Request object)
380
* @param resolutionContext - Optional configuration object with baseUrl for URL matching
381
* @returns Promise resolving to Response object if a handler matches, undefined otherwise
382
*/
383
function getResponse(
384
handlers: Array<RequestHandler>,
385
request: Request,
386
resolutionContext?: ResponseResolutionContext
387
): Promise<Response | undefined>;
388
389
interface ResponseResolutionContext {
390
baseUrl?: string;
391
}
392
```
393
394
**Usage Examples:**
395
396
```typescript
397
import { getResponse, http, HttpResponse } from "msw";
398
399
// Basic usage - resolve request against handlers
400
const handlers = [
401
http.get('/api/user', () => HttpResponse.json({ name: 'John' })),
402
http.post('/api/user', () => new Response('Created', { status: 201 }))
403
];
404
405
const request = new Request('https://example.com/api/user');
406
const response = await getResponse(handlers, request);
407
// Returns: Response with JSON { name: 'John' }
408
409
// Advanced: Batched GraphQL operations
410
function batchedGraphQLQuery(url: string, handlers: Array<RequestHandler>) {
411
return http.post(url, async ({ request }) => {
412
const operations = await request.json();
413
414
const responses = await Promise.all(
415
operations.map(async (operation) => {
416
const scopedRequest = new Request(request, {
417
body: JSON.stringify(operation),
418
});
419
const response = await getResponse(handlers, scopedRequest);
420
return response || fetch(bypass(scopedRequest));
421
})
422
);
423
424
return HttpResponse.json(responses.map(r => r.json()));
425
});
426
}
427
428
// Custom handler composition
429
const compositeHandler = http.get('/api/data', async ({ request }) => {
430
// Try specialized handlers first
431
const specializedResponse = await getResponse(specializedHandlers, request);
432
if (specializedResponse) {
433
return specializedResponse;
434
}
435
436
// Fall back to default behavior
437
return HttpResponse.json({ message: 'Default response' });
438
});
439
440
// Testing utility - programmatically test handler behavior
441
const testHandlers = [
442
http.get('/test', () => HttpResponse.json({ test: true }))
443
];
444
445
const testRequest = new Request('http://localhost/test');
446
const testResponse = await getResponse(testHandlers, testRequest);
447
console.log(await testResponse?.json()); // { test: true }
448
```
449
450
### URL Matching Utilities
451
452
Utilities for working with URL patterns and matching.
453
454
```typescript { .api }
455
/**
456
* Match request URL against a predicate pattern
457
* @param url - URL to match against
458
* @param predicate - String pattern, RegExp, or Path to match
459
* @returns Match result with success status and extracted parameters
460
*/
461
function matchRequestUrl(url: URL, predicate: Path): Match;
462
463
/**
464
* Clean and normalize URL for consistent matching
465
* @param url - URL string to clean
466
* @returns Cleaned URL string
467
*/
468
function cleanUrl(url: string): string;
469
470
type Path = string | RegExp;
471
472
interface Match<Params = Record<string, string>> {
473
matches: boolean;
474
params: Params;
475
}
476
```
477
478
**Usage Examples:**
479
480
```typescript
481
import { matchRequestUrl, cleanUrl } from "msw";
482
483
// Custom request matching logic
484
function customMatcher(request: Request): boolean {
485
const url = new URL(request.url);
486
const match = matchRequestUrl(url, '/api/users/:id');
487
488
if (match.matches) {
489
console.log('Matched user ID:', match.params.id);
490
return true;
491
}
492
493
return false;
494
}
495
496
// URL cleaning for consistent matching
497
function normalizeUrl(url: string): string {
498
const cleaned = cleanUrl(url);
499
console.log('Original:', url);
500
console.log('Cleaned:', cleaned);
501
return cleaned;
502
}
503
504
// Pattern matching examples
505
const testCases = [
506
{ url: 'https://api.example.com/users/123', pattern: '/users/:id' },
507
{ url: 'https://api.example.com/posts?page=1', pattern: '/posts' },
508
{ url: 'https://api.example.com/files/doc.pdf', pattern: /\/files\/.+\.pdf$/ }
509
];
510
511
testCases.forEach(({ url, pattern }) => {
512
const match = matchRequestUrl(new URL(url), pattern);
513
console.log(`${url} matches ${pattern}:`, match);
514
});
515
```
516
517
### Asset Request Detection
518
519
Utility to identify common asset requests that typically shouldn't be mocked.
520
521
```typescript { .api }
522
/**
523
* Check if request is for a common static asset
524
* @param request - Request object to check
525
* @returns True if request appears to be for a static asset
526
*/
527
function isCommonAssetRequest(request: Request): boolean;
528
```
529
530
**Usage Examples:**
531
532
```typescript
533
import { http, HttpResponse, isCommonAssetRequest, passthrough } from "msw";
534
535
// Skip mocking for asset requests
536
http.all('*', ({ request }) => {
537
// Let asset requests pass through to real server
538
if (isCommonAssetRequest(request)) {
539
return passthrough();
540
}
541
542
// Continue with API mocking for non-asset requests
543
return HttpResponse.json({ intercepted: true });
544
});
545
546
// Conditional handling based on request type
547
http.get('*', ({ request }) => {
548
const url = new URL(request.url);
549
550
if (isCommonAssetRequest(request)) {
551
console.log('Asset request detected:', url.pathname);
552
return passthrough();
553
}
554
555
if (url.pathname.startsWith('/api/')) {
556
return HttpResponse.json({ api: 'mocked' });
557
}
558
559
// Default fallback
560
return passthrough();
561
});
562
563
// Logging asset vs API requests
564
http.all('*', ({ request }) => {
565
const isAsset = isCommonAssetRequest(request);
566
567
console.log(`Request type: ${isAsset ? 'Asset' : 'API'} - ${request.url}`);
568
569
if (isAsset) {
570
return passthrough();
571
}
572
573
return HttpResponse.text('Caught by MSW');
574
});
575
```
576
577
### Advanced Utility Patterns
578
579
Combine utilities for sophisticated request handling scenarios.
580
581
```typescript
582
// Smart passthrough with delay simulation
583
http.get('/api/smart-proxy', async ({ request }) => {
584
const url = new URL(request.url);
585
const simulateDelay = url.searchParams.get('delay');
586
587
if (simulateDelay) {
588
await delay(parseInt(simulateDelay));
589
}
590
591
// Pass through to real API after delay
592
return passthrough();
593
});
594
595
// Conditional bypass based on request content
596
http.post('/api/conditional-bypass', async ({ request }) => {
597
const body = await request.json();
598
599
// Bypass for real data, mock for test data
600
if (body.useRealApi === true) {
601
// Create new request with bypass
602
return fetch(bypass(request.url, {
603
method: request.method,
604
body: JSON.stringify(body),
605
headers: request.headers
606
}));
607
}
608
609
await delay(100);
610
return HttpResponse.json({
611
mocked: true,
612
received: body
613
});
614
});
615
616
// Hybrid real/mock response
617
http.get('/api/hybrid-data', async ({ request }) => {
618
try {
619
// Try to get real data
620
const realResponse = await fetch(bypass(request.url));
621
const realData = await realResponse.json();
622
623
// Combine with mock data
624
await delay(50); // Add some realistic delay
625
626
return HttpResponse.json({
627
real: realData,
628
mock: { timestamp: Date.now(), generated: true }
629
});
630
} catch (error) {
631
// Fall back to pure mock if real API fails
632
await delay(100);
633
return HttpResponse.json({
634
real: null,
635
mock: { error: 'Real API unavailable', fallback: true },
636
timestamp: Date.now()
637
});
638
}
639
});
640
```
641
642
## Types
643
644
```typescript { .api }
645
// Delay types
646
type DelayMode = 'real' | 'infinite';
647
648
// Bypass types
649
type BypassRequestInput = string | URL | Request;
650
651
// URL matching types
652
type Path = string | RegExp;
653
type PathParams<T extends string = string> = Record<T, string>;
654
655
interface Match<Params = Record<string, string>> {
656
matches: boolean;
657
params: Params;
658
}
659
660
// Utility function signatures
661
declare function delay(durationOrMode?: DelayMode | number): Promise<void>;
662
declare function bypass(input: BypassRequestInput, init?: RequestInit): Request;
663
declare function passthrough(): HttpResponse<any>;
664
declare function matchRequestUrl(url: URL, predicate: Path): Match;
665
declare function cleanUrl(url: string): string;
666
declare function isCommonAssetRequest(request: Request): boolean;
667
668
// Constants
669
declare const SET_TIMEOUT_MAX_ALLOWED_INT: number;
670
declare const MIN_SERVER_RESPONSE_TIME: number;
671
declare const MAX_SERVER_RESPONSE_TIME: number;
672
declare const NODE_SERVER_RESPONSE_TIME: number;
673
```