0
# Fixture Management
1
2
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.
3
4
## Capabilities
5
6
### Fixtures Directory
7
8
Configure the directory where recorded HTTP responses are stored.
9
10
```javascript { .api }
11
/**
12
* Directory path where recorded HTTP responses are stored
13
* @type {string}
14
*/
15
Replay.fixtures: string;
16
```
17
18
**Usage Examples:**
19
20
```javascript
21
const Replay = require('replay');
22
23
// Set fixtures directory
24
Replay.fixtures = __dirname + '/test/fixtures';
25
Replay.fixtures = './fixtures/replay';
26
Replay.fixtures = '/absolute/path/to/fixtures';
27
28
// Get current fixtures directory
29
console.log('Fixtures stored in:', Replay.fixtures);
30
```
31
32
**Directory Structure:**
33
34
Fixtures are organized by hostname:
35
```
36
fixtures/
37
├── api.example.com/
38
│ ├── 20231201_120000_001
39
│ └── 20231201_120030_002
40
├── cdn.example.com/
41
│ └── 20231201_120100_001
42
└── localhost_3000/
43
└── 20231201_120200_001
44
```
45
46
### Header Configuration
47
48
Configure which HTTP headers are recorded and used for request matching.
49
50
```javascript { .api }
51
/**
52
* Array of regular expressions defining which headers to record/match
53
* @type {RegExp[]}
54
*/
55
Replay.headers: RegExp[];
56
```
57
58
**Default Headers:**
59
- Headers starting with `Accept` (e.g., Accept-Encoding, Accept-Language)
60
- `Authorization` header
61
- `Body` header (for POST request body matching)
62
- `Content-Type` header
63
- `Host` header
64
- Headers starting with `If-` (e.g., If-Modified-Since, If-None-Match)
65
- Headers starting with `X-` (e.g., X-Requested-With, X-API-Key)
66
67
**Usage Examples:**
68
69
```javascript
70
const Replay = require('replay');
71
72
// Add custom headers to record/match
73
Replay.headers.push(/^content-length/);
74
Replay.headers.push(/^user-agent/);
75
76
// Replace default headers with custom set
77
Replay.headers = [
78
/^accept/,
79
/^authorization/,
80
/^content-type/,
81
/^x-api-key/
82
];
83
84
// View current header patterns
85
console.log('Recording headers:', Replay.headers);
86
```
87
88
### Automatic Response Recording
89
90
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.
91
92
**Recording Behavior by Mode:**
93
94
- **Record Mode**: All requests to non-configured hosts are recorded when responses are received
95
- **Replay Mode**: No recording occurs, only playback of existing fixtures
96
- **Cheat Mode**: No recording occurs, allows live requests without saving responses
97
- **Bloody Mode**: No recording occurs, all requests go live
98
99
**Response Storage:**
100
101
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.
102
103
### Catalog API
104
105
Direct access to the internal catalog system for advanced fixture management.
106
107
```javascript { .api }
108
/**
109
* Find matching fixtures for a given host
110
* @param {string} host - Hostname to find fixtures for
111
* @returns {Matcher[] | null} Array of matcher functions or null if no fixtures found
112
*/
113
Replay.catalog.find(host: string): Matcher[] | null;
114
115
/**
116
* Save a captured response as a fixture
117
* @param {string} host - Hostname to save fixture for
118
* @param {ReplayRequest} request - Request object
119
* @param {ReplayResponse} response - Response object to save
120
* @param {Function} callback - Callback function called when save completes
121
*/
122
Replay.catalog.save(
123
host: string,
124
request: ReplayRequest,
125
response: ReplayResponse,
126
callback: (error?: Error) => void
127
): void;
128
129
/**
130
* Get current fixtures directory path
131
* @returns {string} Current fixtures directory path
132
*/
133
Replay.catalog.getFixturesDir(): string;
134
135
/**
136
* Set fixtures directory path and clear loaded fixtures
137
* @param {string} dir - New fixtures directory path
138
*/
139
Replay.catalog.setFixturesDir(dir: string): void;
140
```
141
142
**Usage Examples:**
143
144
```javascript
145
const Replay = require('replay');
146
147
// Find fixtures for a specific host
148
const matchers = Replay.catalog.find('api.example.com');
149
if (matchers) {
150
console.log(`Found ${matchers.length} fixtures for api.example.com`);
151
}
152
153
// Get current fixtures directory
154
const fixturesDir = Replay.catalog.getFixturesDir();
155
console.log('Fixtures stored in:', fixturesDir);
156
157
// Change fixtures directory
158
Replay.catalog.setFixturesDir('./new-fixtures');
159
160
// Manual fixture saving (advanced usage)
161
const request = {
162
url: new URL('http://api.example.com/users'),
163
method: 'GET',
164
headers: { 'accept': 'application/json' }
165
};
166
167
const response = {
168
version: '1.1',
169
statusCode: 200,
170
statusMessage: 'OK',
171
rawHeaders: ['Content-Type', 'application/json'],
172
headers: { 'content-type': 'application/json' },
173
body: [[Buffer.from('{"users": []}'), 'utf8']]
174
};
175
176
Replay.catalog.save('api.example.com', request, response, (error) => {
177
if (error) {
178
console.error('Failed to save fixture:', error);
179
} else {
180
console.log('Fixture saved successfully');
181
}
182
});
183
```
184
185
## Fixture File Format
186
187
Fixtures are stored as human-readable text files with a specific format:
188
189
### Format Structure
190
```
191
[HTTP_METHOD] [PATH]
192
[request-header-name]: [request-header-value]
193
[request-header-name]: [request-header-value]
194
195
HTTP/[VERSION] [STATUS_CODE] [STATUS_TEXT]
196
[response-header-name]: [response-header-value]
197
[response-header-name]: [response-header-value]
198
199
[response-body]
200
```
201
202
### Example Fixture File
203
```
204
GET /api/v1/users/123
205
Authorization: Bearer token123
206
Accept: application/json
207
Content-Type: application/json
208
209
HTTP/1.1 200 OK
210
Content-Type: application/json
211
Server: nginx/1.18.0
212
Date: Fri, 02 Dec 2023 12:30:45 GMT
213
Content-Length: 156
214
215
{
216
"id": 123,
217
"name": "John Doe",
218
"email": "john@example.com",
219
"created_at": "2023-12-01T10:00:00Z"
220
}
221
```
222
223
### Regular Expression Support
224
225
Fixtures can use regular expressions for URL matching by adding `REGEXP` between method and path:
226
227
```
228
GET REGEXP /\/users\/\d+/
229
Accept: application/json
230
231
HTTP/1.1 200 OK
232
Content-Type: application/json
233
234
{"id": 123, "name": "User"}
235
```
236
237
## Fixture Management Operations
238
239
### Manual Fixture Editing
240
241
Fixtures can be manually edited for testing specific scenarios:
242
243
```javascript
244
// Original fixture response might be:
245
// HTTP/1.1 200 OK
246
// {"status": "success", "data": [...]}
247
248
// Edit to test error handling:
249
// HTTP/1.1 500 Internal Server Error
250
// {"status": "error", "message": "Database connection failed"}
251
```
252
253
### Fixture File Naming
254
255
- Files are typically named with timestamps: `20231201_120000_001`
256
- Names can be changed to be more descriptive: `user-login-success`, `api-error-500`
257
- Directory structure is based on hostname and port
258
259
### Multiple Responses
260
261
Multiple fixture files in the same host directory allow testing different scenarios:
262
263
```
264
fixtures/api.example.com/
265
├── user-login-success # Successful login
266
├── user-login-invalid-creds # Invalid credentials
267
├── user-login-account-locked # Account locked
268
└── user-profile-not-found # User not found
269
```
270
271
## Environment Integration
272
273
### Debug Mode
274
275
Enable fixture-related debug logging:
276
277
```bash
278
DEBUG=replay npm test
279
```
280
281
This will log fixture loading, matching, and saving operations.
282
283
### Directory Resolution
284
285
Fixture directory paths are resolved relative to the current working directory unless absolute:
286
287
```javascript
288
// Relative paths
289
Replay.fixtures = './fixtures'; // Relative to cwd
290
Replay.fixtures = 'test/fixtures'; // Relative to cwd
291
292
// Absolute paths
293
Replay.fixtures = __dirname + '/fixtures'; // Relative to current file
294
Replay.fixtures = '/var/app/fixtures'; // Absolute path
295
```
296
297
## Error Handling
298
299
### Fixture Loading Errors
300
301
Errors that can occur during fixture loading and playback:
302
303
```javascript { .api }
304
// Thrown when fixture file is corrupted or malformed
305
class CorruptFixtureError extends Error {
306
code: 'CORRUPT FIXTURE';
307
syscall: 'connect';
308
message: string; // Details about the corruption
309
}
310
311
// Thrown when no fixture matches and not in record mode
312
class ConnectionRefusedError extends Error {
313
code: 'ECONNREFUSED';
314
errno: 'ECONNREFUSED';
315
syscall: 'connect';
316
message: 'Error: connect ECONNREFUSED';
317
}
318
```
319
320
**Usage Examples:**
321
322
```javascript
323
const Replay = require('replay');
324
325
// Handle fixture-related errors
326
Replay.on('error', (error) => {
327
if (error.code === 'CORRUPT FIXTURE') {
328
console.error('Fixture file is corrupted:', error.message);
329
// Delete the corrupt fixture file and re-record
330
}
331
332
if (error.code === 'ECONNREFUSED' && Replay.mode === 'replay') {
333
console.error('No fixture found for request. Switch to record mode to capture it.');
334
}
335
});
336
337
// Handle catalog save errors
338
Replay.catalog.save('api.example.com', request, response, (error) => {
339
if (error) {
340
if (error.code === 'ENOENT') {
341
console.error('Fixtures directory does not exist');
342
} else if (error.code === 'EACCES') {
343
console.error('Permission denied writing fixture file');
344
} else {
345
console.error('Failed to save fixture:', error.message);
346
}
347
}
348
});
349
```