or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

fixture-testing.mdglobal-management.mdindex.mdrecording-playback.mdrequest-interception.mdrequest-matching.mdresponse-definition.md

fixture-testing.mddocs/

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

```