HTTP request recording and replay system for API testing
—
Replay's fixture management system handles the recording, storage, and retrieval of HTTP response fixtures, providing fine-grained control over what gets recorded and how it's stored.
Configure the directory where recorded HTTP responses are stored.
/**
* Directory path where recorded HTTP responses are stored
* @type {string}
*/
Replay.fixtures: string;Usage Examples:
const Replay = require('replay');
// Set fixtures directory
Replay.fixtures = __dirname + '/test/fixtures';
Replay.fixtures = './fixtures/replay';
Replay.fixtures = '/absolute/path/to/fixtures';
// Get current fixtures directory
console.log('Fixtures stored in:', Replay.fixtures);Directory Structure:
Fixtures are organized by hostname:
fixtures/
├── api.example.com/
│ ├── 20231201_120000_001
│ └── 20231201_120030_002
├── cdn.example.com/
│ └── 20231201_120100_001
└── localhost_3000/
└── 20231201_120200_001Configure which HTTP headers are recorded and used for request matching.
/**
* Array of regular expressions defining which headers to record/match
* @type {RegExp[]}
*/
Replay.headers: RegExp[];Default Headers:
Accept (e.g., Accept-Encoding, Accept-Language)Authorization headerBody header (for POST request body matching)Content-Type headerHost headerIf- (e.g., If-Modified-Since, If-None-Match)X- (e.g., X-Requested-With, X-API-Key)Usage Examples:
const Replay = require('replay');
// Add custom headers to record/match
Replay.headers.push(/^content-length/);
Replay.headers.push(/^user-agent/);
// Replace default headers with custom set
Replay.headers = [
/^accept/,
/^authorization/,
/^content-type/,
/^x-api-key/
];
// View current header patterns
console.log('Recording headers:', Replay.headers);Replay automatically records HTTP responses in record mode. All responses are recorded by default, with the recording behavior controlled by the current mode and host configuration.
Recording Behavior by Mode:
Response Storage:
All successful HTTP responses (regardless of status code) are automatically saved to fixture files when in record mode, unless the host is configured for pass-through, drop, or localhost routing.
Direct access to the internal catalog system for advanced fixture management.
/**
* Find matching fixtures for a given host
* @param {string} host - Hostname to find fixtures for
* @returns {Matcher[] | null} Array of matcher functions or null if no fixtures found
*/
Replay.catalog.find(host: string): Matcher[] | null;
/**
* Save a captured response as a fixture
* @param {string} host - Hostname to save fixture for
* @param {ReplayRequest} request - Request object
* @param {ReplayResponse} response - Response object to save
* @param {Function} callback - Callback function called when save completes
*/
Replay.catalog.save(
host: string,
request: ReplayRequest,
response: ReplayResponse,
callback: (error?: Error) => void
): void;
/**
* Get current fixtures directory path
* @returns {string} Current fixtures directory path
*/
Replay.catalog.getFixturesDir(): string;
/**
* Set fixtures directory path and clear loaded fixtures
* @param {string} dir - New fixtures directory path
*/
Replay.catalog.setFixturesDir(dir: string): void;Usage Examples:
const Replay = require('replay');
// Find fixtures for a specific host
const matchers = Replay.catalog.find('api.example.com');
if (matchers) {
console.log(`Found ${matchers.length} fixtures for api.example.com`);
}
// Get current fixtures directory
const fixturesDir = Replay.catalog.getFixturesDir();
console.log('Fixtures stored in:', fixturesDir);
// Change fixtures directory
Replay.catalog.setFixturesDir('./new-fixtures');
// Manual fixture saving (advanced usage)
const request = {
url: new URL('http://api.example.com/users'),
method: 'GET',
headers: { 'accept': 'application/json' }
};
const response = {
version: '1.1',
statusCode: 200,
statusMessage: 'OK',
rawHeaders: ['Content-Type', 'application/json'],
headers: { 'content-type': 'application/json' },
body: [[Buffer.from('{"users": []}'), 'utf8']]
};
Replay.catalog.save('api.example.com', request, response, (error) => {
if (error) {
console.error('Failed to save fixture:', error);
} else {
console.log('Fixture saved successfully');
}
});Fixtures are stored as human-readable text files with a specific format:
[HTTP_METHOD] [PATH]
[request-header-name]: [request-header-value]
[request-header-name]: [request-header-value]
HTTP/[VERSION] [STATUS_CODE] [STATUS_TEXT]
[response-header-name]: [response-header-value]
[response-header-name]: [response-header-value]
[response-body]GET /api/v1/users/123
Authorization: Bearer token123
Accept: application/json
Content-Type: application/json
HTTP/1.1 200 OK
Content-Type: application/json
Server: nginx/1.18.0
Date: Fri, 02 Dec 2023 12:30:45 GMT
Content-Length: 156
{
"id": 123,
"name": "John Doe",
"email": "john@example.com",
"created_at": "2023-12-01T10:00:00Z"
}Fixtures can use regular expressions for URL matching by adding REGEXP between method and path:
GET REGEXP /\/users\/\d+/
Accept: application/json
HTTP/1.1 200 OK
Content-Type: application/json
{"id": 123, "name": "User"}Fixtures can be manually edited for testing specific scenarios:
// Original fixture response might be:
// HTTP/1.1 200 OK
// {"status": "success", "data": [...]}
// Edit to test error handling:
// HTTP/1.1 500 Internal Server Error
// {"status": "error", "message": "Database connection failed"}20231201_120000_001user-login-success, api-error-500Multiple fixture files in the same host directory allow testing different scenarios:
fixtures/api.example.com/
├── user-login-success # Successful login
├── user-login-invalid-creds # Invalid credentials
├── user-login-account-locked # Account locked
└── user-profile-not-found # User not foundEnable fixture-related debug logging:
DEBUG=replay npm testThis will log fixture loading, matching, and saving operations.
Fixture directory paths are resolved relative to the current working directory unless absolute:
// Relative paths
Replay.fixtures = './fixtures'; // Relative to cwd
Replay.fixtures = 'test/fixtures'; // Relative to cwd
// Absolute paths
Replay.fixtures = __dirname + '/fixtures'; // Relative to current file
Replay.fixtures = '/var/app/fixtures'; // Absolute pathErrors that can occur during fixture loading and playback:
// Thrown when fixture file is corrupted or malformed
class CorruptFixtureError extends Error {
code: 'CORRUPT FIXTURE';
syscall: 'connect';
message: string; // Details about the corruption
}
// Thrown when no fixture matches and not in record mode
class ConnectionRefusedError extends Error {
code: 'ECONNREFUSED';
errno: 'ECONNREFUSED';
syscall: 'connect';
message: 'Error: connect ECONNREFUSED';
}Usage Examples:
const Replay = require('replay');
// Handle fixture-related errors
Replay.on('error', (error) => {
if (error.code === 'CORRUPT FIXTURE') {
console.error('Fixture file is corrupted:', error.message);
// Delete the corrupt fixture file and re-record
}
if (error.code === 'ECONNREFUSED' && Replay.mode === 'replay') {
console.error('No fixture found for request. Switch to record mode to capture it.');
}
});
// Handle catalog save errors
Replay.catalog.save('api.example.com', request, response, (error) => {
if (error) {
if (error.code === 'ENOENT') {
console.error('Fixtures directory does not exist');
} else if (error.code === 'EACCES') {
console.error('Permission denied writing fixture file');
} else {
console.error('Failed to save fixture:', error.message);
}
}
});Install with Tessl CLI
npx tessl i tessl/npm-replay