A comprehensive XMLHttpRequest mocking utility for testing and prototyping web applications.
xhr-mock provides several utility functions for advanced mocking scenarios including delays, one-time responses, response sequences, and request proxying to real servers.
Creates a mock handler that only responds to the first request, then becomes inactive.
/**
* Create a mock that only responds once
* @param mock - Mock object or function to use for the single response
* @returns MockFunction that responds only to the first matching request
*/
function once(mock: MockFunction | MockObject): MockFunction;Usage Examples:
import mock, { once } from 'xhr-mock';
// One-time response with object
mock.get('/api/special-offer', once({
status: 200,
body: JSON.stringify({ offer: 'Limited time 50% off!' })
}));
// One-time response with function
mock.post('/api/trial-signup', once((req, res) => {
const email = JSON.parse(req.body()).email;
return res.status(201).body(JSON.stringify({
message: 'Trial activated',
email
}));
}));
// After the first request, subsequent requests will not match this handlerAdds a delay to mock responses to simulate network latency or slow servers.
/**
* Add delay to mock responses
* @param mock - Mock object or function to delay
* @param ms - Delay in milliseconds (default: 1500)
* @returns MockFunction that responds after the specified delay
*/
function delay(mock: MockFunction | MockObject, ms?: number): MockFunction;Usage Examples:
import mock, { delay } from 'xhr-mock';
// Default delay (1500ms)
mock.get('/api/slow-endpoint', delay({
status: 200,
body: JSON.stringify({ data: 'This took a while...' })
}));
// Custom delay (3 seconds)
mock.post('/api/upload', delay({
status: 201,
body: JSON.stringify({ message: 'Upload complete' })
}, 3000));
// Delay with function
mock.get('/api/processing', delay((req, res) => {
return res.status(200).body(JSON.stringify({
status: 'processed',
timestamp: Date.now()
}));
}, 2000));Returns different responses for consecutive requests to the same endpoint.
/**
* Return different responses for consecutive requests
* @param mocks - Array of mock objects or functions to use in sequence
* @returns MockFunction that cycles through the provided mocks
*/
function sequence(mocks: (MockFunction | MockObject)[]): MockFunction;Usage Examples:
import mock, { sequence } from 'xhr-mock';
// Different status codes for each request
mock.get('/api/flaky-service', sequence([
{ status: 200, body: 'Success on first try' },
{ status: 500, body: 'Server error on second try' },
{ status: 200, body: 'Success on third try' }
]));
// Mixed objects and functions
mock.post('/api/batch-process', sequence([
(req, res) => res.status(202).body('Processing started'),
{ status: 202, body: 'Still processing...' },
{ status: 200, body: 'Processing complete' }
// After the third request, no more responses (undefined returned)
]));
// First request gets first response, second gets second, etc.
// After all responses are used, subsequent requests get no responseForwards unhandled requests to real servers, allowing you to mock some endpoints while letting others pass through.
/**
* Proxy unhandled requests to real servers
* @param req - MockRequest object representing the incoming request
* @param res - MockResponse object to populate with the real response
* @returns Promise resolving to MockResponse with real server data
*/
function proxy(req: MockRequest, res: MockResponse): Promise<MockResponse>;Usage Examples:
import mock, { proxy } from 'xhr-mock';
mock.setup();
// Mock specific endpoints
mock.post('/api/auth/login', {
status: 200,
body: JSON.stringify({ token: 'fake-jwt-token' })
});
// Proxy all other requests to real servers
mock.use(proxy);
// This request will be mocked
fetch('/api/auth/login', {
method: 'POST',
body: JSON.stringify({ username: 'test', password: 'test' })
});
// This request will be proxied to the real server
fetch('https://api.github.com/users/octocat');Advanced Proxy Usage:
// Selective proxying with custom logic
mock.use((req, res) => {
const url = req.url();
// Mock localhost requests
if (url.host === 'localhost') {
return res.status(200).body('Mocked localhost response');
}
// Proxy external requests
if (url.host.includes('api.external.com')) {
return proxy(req, res);
}
// Default response for everything else
return res.status(404).body('Not found');
});Utilities can be combined to create complex mocking scenarios:
import mock, { once, delay, sequence } from 'xhr-mock';
// One-time delayed response
mock.get('/api/setup', once(delay({
status: 200,
body: JSON.stringify({ initialized: true })
}, 2000)));
// Delayed sequence
mock.get('/api/polling', delay(sequence([
{ status: 202, body: 'Processing...' },
{ status: 202, body: 'Still processing...' },
{ status: 200, body: 'Complete!' }
]), 1000));
// Complex scenario: First request is delayed, subsequent requests are fast
let firstRequest = true;
mock.get('/api/cache-warmup', (req, res) => {
const response = res.status(200).body('Data loaded');
if (firstRequest) {
firstRequest = false;
return delay(() => response, 3000)(req, res);
}
return response;
});Create realistic error scenarios for testing error handling:
// Simulate network errors
mock.get('/api/unreliable', () => {
return Promise.reject(new Error('Network error'));
});
// Simulate timeouts (return a promise that never resolves)
mock.get('/api/timeout', () => {
return new Promise(() => {}); // Never resolves
});
// Then in your test setup:
const xhr = new XMLHttpRequest();
xhr.timeout = 1000; // 1 second timeout
xhr.ontimeout = () => console.log('Request timed out');
xhr.onerror = () => console.log('Network error occurred');type MockFunction = (
request: MockRequest,
response: MockResponse
) => undefined | MockResponse | Promise<undefined | MockResponse>;
interface MockObject {
status?: number;
reason?: string;
headers?: MockHeaders;
body?: any;
}
type MockHeaders = {[name: string]: string};Install with Tessl CLI
npx tessl i tessl/npm-xhr-mock