CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-replay

HTTP request recording and replay system for API testing

Pending
Overview
Eval results
Files

fixture-management.mddocs/

Fixture Management

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.

Capabilities

Fixtures Directory

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_001

Header Configuration

Configure 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:

  • Headers starting with Accept (e.g., Accept-Encoding, Accept-Language)
  • Authorization header
  • Body header (for POST request body matching)
  • Content-Type header
  • Host header
  • Headers starting with If- (e.g., If-Modified-Since, If-None-Match)
  • Headers starting with 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);

Automatic Response Recording

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:

  • Record Mode: All requests to non-configured hosts are recorded when responses are received
  • Replay Mode: No recording occurs, only playback of existing fixtures
  • Cheat Mode: No recording occurs, allows live requests without saving responses
  • Bloody Mode: No recording occurs, all requests go live

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.

Catalog API

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

Fixture File Format

Fixtures are stored as human-readable text files with a specific format:

Format Structure

[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]

Example Fixture File

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"
}

Regular Expression Support

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"}

Fixture Management Operations

Manual Fixture Editing

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"}

Fixture File Naming

  • Files are typically named with timestamps: 20231201_120000_001
  • Names can be changed to be more descriptive: user-login-success, api-error-500
  • Directory structure is based on hostname and port

Multiple Responses

Multiple 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 found

Environment Integration

Debug Mode

Enable fixture-related debug logging:

DEBUG=replay npm test

This will log fixture loading, matching, and saving operations.

Directory Resolution

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 path

Error Handling

Fixture Loading Errors

Errors 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

docs

fixture-management.md

host-configuration.md

index.md

mode-management.md

proxy-system.md

tile.json