0
# Fixture-Based Testing (Back Mode)
1
2
This document covers nock's "Back" mode, an advanced fixture-based testing system that automatically manages HTTP fixtures with different operational modes for various testing scenarios.
3
4
## Back Interface
5
6
The Back system provides a workflow for fixture-based testing with automatic fixture management.
7
8
```javascript { .api }
9
interface Back {
10
(fixtureName: string, nockedFn: (nockDone: () => void) => void): void;
11
(
12
fixtureName: string,
13
options: BackOptions,
14
nockedFn: (nockDone: () => void) => void
15
): void;
16
(fixtureName: string, options?: BackOptions): Promise<{
17
nockDone: () => void;
18
context: BackContext;
19
}>;
20
21
currentMode: BackMode;
22
fixtures: string;
23
setMode(mode: BackMode): void;
24
}
25
26
const back: Back;
27
```
28
29
## Back Modes
30
31
Back operates in different modes that control how fixtures are handled:
32
33
```javascript { .api }
34
type BackMode = 'wild' | 'dryrun' | 'record' | 'update' | 'lockdown';
35
```
36
37
### Mode Descriptions
38
39
- **'wild'**: Allow all real HTTP requests, no fixture loading or recording
40
- **'dryrun'**: Default mode, load fixtures if they exist, allow real requests if no fixture
41
- **'record'**: Record real HTTP requests and save them as fixtures
42
- **'update'**: Update existing fixtures with new recordings
43
- **'lockdown'**: Only use existing fixtures, throw error if fixture missing
44
45
### Setting Modes
46
47
```javascript
48
const nock = require("nock");
49
50
// Set mode globally
51
nock.back.setMode("record");
52
53
// Check current mode
54
console.log(nock.back.currentMode); // 'record'
55
56
// Can also be set via environment variable
57
process.env.NOCK_BACK_MODE = "lockdown";
58
```
59
60
## Fixture Directory
61
62
Configure where fixtures are stored:
63
64
```javascript
65
// Set fixture directory
66
nock.back.fixtures = "./test/fixtures";
67
68
// All fixture files will be created/loaded from this directory
69
```
70
71
## Basic Usage Patterns
72
73
### Callback Style
74
75
```javascript { .api }
76
back(fixtureName: string, nockedFn: (nockDone: () => void) => void): void;
77
```
78
79
```javascript
80
const nock = require("nock");
81
82
nock.back.fixtures = "./test/fixtures";
83
nock.back.setMode("dryrun");
84
85
describe("API Tests", () => {
86
it("should get user data", (done) => {
87
nock.back("get-user.json", (nockDone) => {
88
// Your test code here
89
fetch("https://api.example.com/users/1")
90
.then(response => response.json())
91
.then(user => {
92
expect(user.id).toBe(1);
93
nockDone(); // Mark nock interactions as complete
94
done();
95
});
96
});
97
});
98
});
99
```
100
101
### Promise Style
102
103
```javascript { .api }
104
back(fixtureName: string, options?: BackOptions): Promise<{
105
nockDone: () => void;
106
context: BackContext;
107
}>;
108
```
109
110
```javascript
111
describe("API Tests", () => {
112
it("should get user data", async () => {
113
const { nockDone, context } = await nock.back("get-user.json");
114
115
try {
116
const response = await fetch("https://api.example.com/users/1");
117
const user = await response.json();
118
119
expect(user.id).toBe(1);
120
121
// Verify all fixtures were used
122
context.assertScopesFinished();
123
} finally {
124
nockDone();
125
}
126
});
127
});
128
```
129
130
## Back Options
131
132
```javascript { .api }
133
interface BackOptions {
134
before?: (def: Definition) => void;
135
after?: (scope: Scope) => void;
136
afterRecord?: (defs: Definition[]) => Definition[] | string;
137
recorder?: RecorderOptions;
138
}
139
```
140
141
### Before Hook
142
143
Process fixture definitions before they're loaded:
144
145
```javascript
146
nock.back("api-test.json", {
147
before: (def) => {
148
// Modify fixture definition before loading
149
if (def.path === "/users") {
150
def.response = { users: [], modified: true };
151
}
152
153
// Remove sensitive headers
154
if (def.reqheaders) {
155
delete def.reqheaders.authorization;
156
}
157
}
158
}, (nockDone) => {
159
// Test code...
160
nockDone();
161
});
162
```
163
164
### After Hook
165
166
Process loaded scopes after fixture loading:
167
168
```javascript
169
nock.back("api-test.json", {
170
after: (scope) => {
171
// Add additional configuration to loaded scopes
172
scope.defaultReplyHeaders({
173
"X-Test-Mode": "fixture"
174
});
175
}
176
}, (nockDone) => {
177
// Test code...
178
nockDone();
179
});
180
```
181
182
### After Record Hook
183
184
Process recorded fixtures before saving:
185
186
```javascript
187
nock.back.setMode("record");
188
189
nock.back("api-test.json", {
190
afterRecord: (defs) => {
191
// Process recorded definitions
192
return defs.map(def => {
193
// Remove sensitive data
194
if (def.reqheaders) {
195
delete def.reqheaders.authorization;
196
delete def.reqheaders.cookie;
197
}
198
199
// Sanitize response data
200
if (typeof def.response === "string") {
201
try {
202
const response = JSON.parse(def.response);
203
if (response.email) {
204
response.email = "user@example.com";
205
}
206
def.response = JSON.stringify(response);
207
} catch (e) {
208
// Leave non-JSON responses as-is
209
}
210
}
211
212
return def;
213
});
214
}
215
}, (nockDone) => {
216
// Test code that will be recorded...
217
nockDone();
218
});
219
```
220
221
### Custom Recorder Options
222
223
```javascript
224
nock.back("api-test.json", {
225
recorder: {
226
output_objects: true,
227
enable_reqheaders_recording: true,
228
dont_print: true
229
}
230
}, (nockDone) => {
231
// Test code...
232
nockDone();
233
});
234
```
235
236
## Back Context
237
238
The context object provides information about loaded fixtures and verification methods.
239
240
```javascript { .api }
241
interface BackContext {
242
isLoaded: boolean;
243
scopes: Scope[];
244
assertScopesFinished(): void;
245
query: InterceptorSurface[];
246
}
247
248
interface InterceptorSurface {
249
method: string;
250
uri: string;
251
basePath: string;
252
path: string;
253
queries?: string;
254
counter: number;
255
body: string;
256
statusCode: number;
257
optional: boolean;
258
}
259
```
260
261
### Using Context
262
263
```javascript
264
const { nockDone, context } = await nock.back("complex-test.json");
265
266
console.log("Fixture loaded:", context.isLoaded);
267
console.log("Number of scopes:", context.scopes.length);
268
console.log("Interceptor details:", context.query);
269
270
try {
271
// Run your tests...
272
273
// Verify all interceptors were used
274
context.assertScopesFinished();
275
276
} finally {
277
nockDone();
278
}
279
```
280
281
## Mode-Specific Workflows
282
283
### Record Mode Workflow
284
285
```javascript
286
// Set up recording
287
nock.back.fixtures = "./test/fixtures";
288
nock.back.setMode("record");
289
290
describe("Recording API Interactions", () => {
291
it("should record user API calls", (done) => {
292
nock.back("user-api.json", {
293
afterRecord: (defs) => {
294
console.log(`Recorded ${defs.length} interactions`);
295
return defs;
296
}
297
}, (nockDone) => {
298
// Make real API calls - these will be recorded
299
Promise.all([
300
fetch("https://api.example.com/users"),
301
fetch("https://api.example.com/users/1"),
302
fetch("https://api.example.com/users", {
303
method: "POST",
304
headers: { "Content-Type": "application/json" },
305
body: JSON.stringify({ name: "Alice" })
306
})
307
]).then(() => {
308
nockDone();
309
done();
310
});
311
});
312
});
313
});
314
```
315
316
### Lockdown Mode Workflow
317
318
```javascript
319
// Set up lockdown mode for CI/production tests
320
nock.back.fixtures = "./test/fixtures";
321
nock.back.setMode("lockdown");
322
323
describe("Lockdown Tests", () => {
324
it("should use only existing fixtures", async () => {
325
try {
326
const { nockDone, context } = await nock.back("existing-fixture.json");
327
328
// This will only work if the fixture exists
329
const response = await fetch("https://api.example.com/users");
330
const users = await response.json();
331
332
expect(Array.isArray(users)).toBe(true);
333
context.assertScopesFinished();
334
nockDone();
335
336
} catch (error) {
337
if (error.message.includes("fixture")) {
338
console.error("Fixture not found - run in record mode first");
339
}
340
throw error;
341
}
342
});
343
});
344
```
345
346
### Update Mode Workflow
347
348
```javascript
349
// Update existing fixtures with new data
350
nock.back.setMode("update");
351
352
describe("Update Fixtures", () => {
353
it("should update existing fixture", (done) => {
354
nock.back("user-api.json", {
355
afterRecord: (defs) => {
356
console.log("Updated fixture with new recordings");
357
return defs;
358
}
359
}, (nockDone) => {
360
// Make API calls - these will replace the existing fixture
361
fetch("https://api.example.com/users/1")
362
.then(() => {
363
nockDone();
364
done();
365
});
366
});
367
});
368
});
369
```
370
371
## Complete Example: Multi-Mode Test Suite
372
373
```javascript
374
const nock = require("nock");
375
376
// Configure fixtures directory
377
nock.back.fixtures = "./test/fixtures";
378
379
// Set mode based on environment
380
const mode = process.env.NOCK_BACK_MODE || "dryrun";
381
nock.back.setMode(mode);
382
383
describe("User API Integration Tests", () => {
384
beforeEach(() => {
385
// Clean up before each test
386
nock.cleanAll();
387
});
388
389
it("should handle user creation flow", async () => {
390
const { nockDone, context } = await nock.back("user-creation.json", {
391
before: (def) => {
392
// Sanitize any recorded auth headers
393
if (def.reqheaders && def.reqheaders.authorization) {
394
def.reqheaders.authorization = "Bearer <redacted>";
395
}
396
},
397
398
afterRecord: (defs) => {
399
// Process recorded fixtures
400
return defs.map(def => {
401
// Remove sensitive response data
402
if (typeof def.response === "string") {
403
try {
404
const response = JSON.parse(def.response);
405
if (response.apiKey) delete response.apiKey;
406
if (response.internalId) delete response.internalId;
407
def.response = JSON.stringify(response);
408
} catch (e) {
409
// Leave non-JSON responses as-is
410
}
411
}
412
return def;
413
});
414
}
415
});
416
417
try {
418
// Test the user creation flow
419
const createResponse = await fetch("https://api.example.com/users", {
420
method: "POST",
421
headers: {
422
"Content-Type": "application/json",
423
"Authorization": "Bearer test-token"
424
},
425
body: JSON.stringify({
426
name: "Alice",
427
email: "alice@example.com"
428
})
429
});
430
431
expect(createResponse.status).toBe(201);
432
const newUser = await createResponse.json();
433
expect(newUser.id).toBeDefined();
434
435
// Verify user was created
436
const getResponse = await fetch(`https://api.example.com/users/${newUser.id}`);
437
expect(getResponse.status).toBe(200);
438
const fetchedUser = await getResponse.json();
439
expect(fetchedUser.name).toBe("Alice");
440
441
// Verify all fixtures were used
442
context.assertScopesFinished();
443
444
} finally {
445
nockDone();
446
}
447
});
448
});
449
```
450
451
## Environment Configuration
452
453
### Environment Variables
454
455
```bash
456
# Set mode via environment variable
457
export NOCK_BACK_MODE=record
458
459
# Run tests
460
npm test
461
462
# Change to lockdown for CI
463
export NOCK_BACK_MODE=lockdown
464
npm test
465
```
466
467
### Package.json Scripts
468
469
```json
470
{
471
"scripts": {
472
"test": "jest",
473
"test:record": "NOCK_BACK_MODE=record jest",
474
"test:update": "NOCK_BACK_MODE=update jest",
475
"test:lockdown": "NOCK_BACK_MODE=lockdown jest"
476
}
477
}
478
```
479
480
## Best Practices
481
482
### Fixture Organization
483
484
```javascript
485
// Organize fixtures by feature/test suite
486
nock.back.fixtures = "./test/fixtures/user-api";
487
488
// Use descriptive fixture names
489
await nock.back("create-user-success.json");
490
await nock.back("create-user-validation-error.json");
491
await nock.back("get-user-not-found.json");
492
```
493
494
### Sanitization
495
496
Always sanitize recorded fixtures:
497
498
```javascript
499
const sanitizeFixture = (defs) => {
500
return defs.map(def => {
501
// Remove sensitive headers
502
if (def.reqheaders) {
503
delete def.reqheaders.authorization;
504
delete def.reqheaders.cookie;
505
delete def.reqheaders["x-api-key"];
506
}
507
508
// Sanitize response data
509
if (typeof def.response === "string") {
510
def.response = def.response
511
.replace(/\b\d{16}\b/g, "XXXX-XXXX-XXXX-XXXX") // Credit cards
512
.replace(/\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b/g, "user@example.com"); // Emails
513
}
514
515
return def;
516
});
517
};
518
```
519
520
### Error Handling
521
522
```javascript
523
describe("API Tests with Error Handling", () => {
524
it("should handle missing fixtures gracefully", async () => {
525
try {
526
const { nockDone } = await nock.back("missing-fixture.json");
527
// Test code...
528
nockDone();
529
} catch (error) {
530
if (nock.back.currentMode === "lockdown" && error.message.includes("fixture")) {
531
console.warn("Fixture missing in lockdown mode - this is expected for new tests");
532
// Handle missing fixture appropriately
533
} else {
534
throw error;
535
}
536
}
537
});
538
});
539
```