or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

caching.mdconnection-management.mdcookies.mdcore-http.mderrors.mdglobal-config.mdheaders-body.mdindex.mdinterceptors.mdmock-testing.mdweb-standards.md

mock-testing.mddocs/

0

# Mock Testing Framework

1

2

Comprehensive testing utilities for mocking HTTP requests, recording interactions, and testing HTTP clients with detailed call history tracking.

3

4

## Capabilities

5

6

### MockAgent

7

8

Main mocking agent that intercepts HTTP requests and provides mock responses for testing.

9

10

```javascript { .api }

11

/**

12

* Main mocking agent for HTTP request interception

13

*/

14

class MockAgent extends Dispatcher {

15

constructor(options?: MockAgent.Options);

16

17

get(origin: string | RegExp | URL): MockPool;

18

close(): Promise<void>;

19

destroy(err?: Error): Promise<void>;

20

21

activate(): void;

22

deactivate(): void;

23

enableNetConnect(host?: string | RegExp | ((host: string) => boolean)): void;

24

disableNetConnect(): void;

25

pendingInterceptors(): PendingInterceptor[];

26

assertNoPendingInterceptors(options?: AssertNoPendingInterceptorsOptions): void;

27

}

28

29

interface MockAgent.Options {

30

agent?: Agent;

31

connections?: number;

32

}

33

34

interface AssertNoPendingInterceptorsOptions {

35

pendingInterceptorsFormatter?: PendingInterceptorsFormatter;

36

}

37

38

type PendingInterceptorsFormatter = (pendingInterceptors: PendingInterceptor[]) => string;

39

```

40

41

**Usage Examples:**

42

43

```javascript

44

import { MockAgent, setGlobalDispatcher } from 'undici';

45

46

// Create mock agent

47

const mockAgent = new MockAgent();

48

49

// Set as global dispatcher to intercept all requests

50

setGlobalDispatcher(mockAgent);

51

52

// Allow connections to specific hosts

53

mockAgent.enableNetConnect('api.github.com');

54

55

// Get mock pool for specific origin

56

const mockPool = mockAgent.get('https://api.example.com');

57

58

// Create mock interceptor

59

mockPool.intercept({

60

path: '/users',

61

method: 'GET'

62

}).reply(200, { users: [{ id: 1, name: 'Alice' }] });

63

64

// Make request (will be intercepted)

65

const response = await fetch('https://api.example.com/users');

66

const data = await response.json();

67

console.log(data); // { users: [{ id: 1, name: 'Alice' }] }

68

69

// Assert all interceptors were used

70

mockAgent.assertNoPendingInterceptors();

71

72

// Clean up

73

await mockAgent.close();

74

```

75

76

### MockPool

77

78

Mock pool for intercepting requests to a specific origin with detailed interceptor management.

79

80

```javascript { .api }

81

/**

82

* Mock pool for specific origin request interception

83

*/

84

class MockPool extends Dispatcher {

85

intercept(options: MockInterceptor.Options): MockInterceptor;

86

close(): Promise<void>;

87

destroy(err?: Error): Promise<void>;

88

}

89

90

interface MockInterceptor {

91

reply(statusCode: number, responseBody?: any, responseHeaders?: Record<string, string>): MockScope;

92

reply(callback: MockInterceptor.MockReplyCallback): MockScope;

93

replyWithError(error: Error): MockScope;

94

defaultReplyHeaders(headers: Record<string, string>): MockInterceptor;

95

defaultReplyTrailers(trailers: Record<string, string>): MockInterceptor;

96

replyContentLength(): MockInterceptor;

97

}

98

99

interface MockInterceptor.Options {

100

path: string | RegExp | ((path: string) => boolean);

101

method?: string | RegExp;

102

body?: string | Buffer | Uint8Array | RegExp | ((body: string) => boolean);

103

headers?: Record<string, string | RegExp | ((headerValue: string) => boolean)>;

104

query?: Record<string, any>;

105

}

106

107

type MockInterceptor.MockReplyCallback = (options: {

108

path: string;

109

origin: string;

110

method: string;

111

body: any;

112

headers: Record<string, string>;

113

}) => {

114

statusCode: number;

115

data?: any;

116

responseOptions?: {

117

headers?: Record<string, string>;

118

trailers?: Record<string, string>;

119

};

120

};

121

122

interface MockScope {

123

delay(waitInMs: number): MockScope;

124

persist(): MockScope;

125

times(repeatTimes: number): MockScope;

126

}

127

```

128

129

**Usage Examples:**

130

131

```javascript

132

import { MockAgent, setGlobalDispatcher } from 'undici';

133

134

const mockAgent = new MockAgent();

135

setGlobalDispatcher(mockAgent);

136

137

const mockPool = mockAgent.get('https://api.example.com');

138

139

// Basic mock with static response

140

mockPool.intercept({

141

path: '/users/123',

142

method: 'GET'

143

}).reply(200, { id: 123, name: 'Alice' });

144

145

// Mock with custom headers

146

mockPool.intercept({

147

path: '/api/data',

148

method: 'POST',

149

headers: {

150

'content-type': 'application/json',

151

'authorization': /^Bearer .+/

152

}

153

}).reply(201, { success: true }, {

154

'location': '/api/data/456'

155

});

156

157

// Mock with body matching

158

mockPool.intercept({

159

path: '/api/search',

160

method: 'POST',

161

body: (body) => {

162

const data = JSON.parse(body);

163

return data.query === 'test';

164

}

165

}).reply(200, { results: ['item1', 'item2'] });

166

167

// Mock with query parameters

168

mockPool.intercept({

169

path: '/api/users',

170

method: 'GET',

171

query: { limit: '10', offset: '0' }

172

}).reply(200, { users: [], total: 0 });

173

174

// Dynamic response with callback

175

mockPool.intercept({

176

path: '/api/echo',

177

method: 'POST'

178

}).reply(({ body, headers }) => {

179

return {

180

statusCode: 200,

181

data: { echoed: body, receivedHeaders: headers }

182

};

183

});

184

185

// Mock with scope options

186

mockPool.intercept({

187

path: '/api/slow',

188

method: 'GET'

189

}).reply(200, { data: 'slow response' })

190

.delay(1000) // 1 second delay

191

.times(3) // Only match 3 times

192

.persist(); // Persist after times limit

193

194

// Mock error responses

195

mockPool.intercept({

196

path: '/api/error',

197

method: 'GET'

198

}).replyWithError(new Error('Network error'));

199

200

await mockAgent.close();

201

```

202

203

### MockClient

204

205

Mock client for testing single connection scenarios.

206

207

```javascript { .api }

208

/**

209

* Mock client for single connection testing

210

*/

211

class MockClient extends Dispatcher {

212

constructor(origin: string, options?: MockClient.Options);

213

214

intercept(options: MockInterceptor.Options): MockInterceptor;

215

close(): Promise<void>;

216

destroy(err?: Error): Promise<void>;

217

}

218

219

interface MockClient.Options {

220

agent?: Agent;

221

}

222

```

223

224

**Usage Examples:**

225

226

```javascript

227

import { MockClient } from 'undici';

228

229

// Create mock client for specific origin

230

const mockClient = new MockClient('https://api.example.com');

231

232

// Setup interceptors

233

mockClient.intercept({

234

path: '/health',

235

method: 'GET'

236

}).reply(200, { status: 'ok' });

237

238

// Use mock client directly

239

const response = await mockClient.request({

240

path: '/health',

241

method: 'GET'

242

});

243

244

const data = await response.body.json();

245

console.log(data); // { status: 'ok' }

246

247

await mockClient.close();

248

```

249

250

### SnapshotAgent

251

252

Recording agent for creating HTTP interaction snapshots that can be replayed in tests.

253

254

```javascript { .api }

255

/**

256

* Recording agent for HTTP interaction snapshots

257

*/

258

class SnapshotAgent extends Dispatcher {

259

constructor(origin: string, dispatcher: Dispatcher);

260

261

record(options?: SnapshotAgent.RecordOptions): void;

262

replay(options?: SnapshotAgent.ReplayOptions): void;

263

getRecording(): SnapshotRecording[];

264

setRecording(recording: SnapshotRecording[]): void;

265

266

close(): Promise<void>;

267

destroy(err?: Error): Promise<void>;

268

}

269

270

interface SnapshotAgent.RecordOptions {

271

filter?: (request: SnapshotRequest) => boolean;

272

}

273

274

interface SnapshotAgent.ReplayOptions {

275

strict?: boolean;

276

}

277

278

interface SnapshotRecording {

279

request: SnapshotRequest;

280

response: SnapshotResponse;

281

timestamp: number;

282

}

283

284

interface SnapshotRequest {

285

method: string;

286

path: string;

287

headers: Record<string, string>;

288

body?: string;

289

}

290

291

interface SnapshotResponse {

292

statusCode: number;

293

headers: Record<string, string>;

294

body: string;

295

}

296

```

297

298

**Usage Examples:**

299

300

```javascript

301

import { SnapshotAgent, Agent } from 'undici';

302

import { writeFileSync, readFileSync } from 'fs';

303

304

const realAgent = new Agent();

305

const snapshotAgent = new SnapshotAgent('https://api.example.com', realAgent);

306

307

// Record real HTTP interactions

308

snapshotAgent.record();

309

310

// Make real requests (these will be recorded)

311

await snapshotAgent.request({ path: '/users' });

312

await snapshotAgent.request({ path: '/posts' });

313

314

// Save recording to file

315

const recording = snapshotAgent.getRecording();

316

writeFileSync('test-recording.json', JSON.stringify(recording, null, 2));

317

318

// Later, in tests: load and replay recording

319

const savedRecording = JSON.parse(readFileSync('test-recording.json', 'utf8'));

320

snapshotAgent.setRecording(savedRecording);

321

snapshotAgent.replay();

322

323

// Requests will now return recorded responses

324

const response = await snapshotAgent.request({ path: '/users' });

325

console.log('Replayed response:', await response.body.json());

326

327

await snapshotAgent.close();

328

```

329

330

### MockCallHistory

331

332

Track and verify mock call history for detailed test assertions.

333

334

```javascript { .api }

335

/**

336

* Track mock call history for test verification

337

*/

338

class MockCallHistory {

339

constructor();

340

341

log(call: MockCallHistoryLog): void;

342

getCalls(filter?: MockCallHistoryFilter): MockCallHistoryLog[];

343

getCallCount(filter?: MockCallHistoryFilter): number;

344

hasBeenCalledWith(expectedCall: Partial<MockCallHistoryLog>): boolean;

345

clear(): void;

346

}

347

348

interface MockCallHistoryLog {

349

method: string;

350

path: string;

351

headers: Record<string, string>;

352

body?: any;

353

timestamp: number;

354

origin: string;

355

}

356

357

type MockCallHistoryFilter = (call: MockCallHistoryLog) => boolean;

358

```

359

360

**Usage Examples:**

361

362

```javascript

363

import { MockAgent, MockCallHistory, setGlobalDispatcher } from 'undici';

364

365

const mockAgent = new MockAgent();

366

const callHistory = new MockCallHistory();

367

setGlobalDispatcher(mockAgent);

368

369

// Setup mock with call logging

370

const mockPool = mockAgent.get('https://api.example.com');

371

mockPool.intercept({

372

path: '/api/users',

373

method: 'POST'

374

}).reply(201, { id: 123 });

375

376

// Intercept and log all calls (this would be done internally by undici)

377

const originalRequest = mockPool.request;

378

mockPool.request = function(options) {

379

callHistory.log({

380

method: options.method || 'GET',

381

path: options.path,

382

headers: options.headers || {},

383

body: options.body,

384

timestamp: Date.now(),

385

origin: 'https://api.example.com'

386

});

387

return originalRequest.call(this, options);

388

};

389

390

// Make requests

391

await fetch('https://api.example.com/api/users', {

392

method: 'POST',

393

body: JSON.stringify({ name: 'Alice' })

394

});

395

396

// Verify call history

397

console.log(callHistory.getCallCount()); // 1

398

399

const calls = callHistory.getCalls();

400

console.log(calls[0].method); // 'POST'

401

console.log(calls[0].path); // '/api/users'

402

403

// Check specific call

404

const wasCalledWithCorrectData = callHistory.hasBeenCalledWith({

405

method: 'POST',

406

path: '/api/users'

407

});

408

console.log(wasCalledWithCorrectData); // true

409

410

// Filter calls

411

const postCalls = callHistory.getCalls(call => call.method === 'POST');

412

console.log(postCalls.length); // 1

413

414

await mockAgent.close();

415

```

416

417

### Mock Errors

418

419

Specialized error types for mock testing scenarios.

420

421

```javascript { .api }

422

/**

423

* Mock-specific error types

424

*/

425

const mockErrors: {

426

MockNotMatchedError: typeof MockNotMatchedError;

427

MockInterceptorMismatchError: typeof MockInterceptorMismatchError;

428

};

429

430

class MockNotMatchedError extends Error {

431

constructor(message: string);

432

}

433

434

class MockInterceptorMismatchError extends Error {

435

constructor(message: string);

436

}

437

```

438

439

**Usage Examples:**

440

441

```javascript

442

import { MockAgent, mockErrors, setGlobalDispatcher } from 'undici';

443

444

const mockAgent = new MockAgent();

445

setGlobalDispatcher(mockAgent);

446

447

// Disable real network connections

448

mockAgent.disableNetConnect();

449

450

try {

451

// This will throw since no mock is defined

452

await fetch('https://api.example.com/unmocked');

453

} catch (error) {

454

if (error instanceof mockErrors.MockNotMatchedError) {

455

console.log('No mock interceptor matched the request');

456

}

457

}

458

459

// Setup specific error scenarios

460

const mockPool = mockAgent.get('https://api.example.com');

461

mockPool.intercept({

462

path: '/api/error-test',

463

method: 'GET'

464

}).replyWithError(new mockErrors.MockInterceptorMismatchError('Intentional test error'));

465

466

await mockAgent.close();

467

```

468

469

## Complete Testing Workflow

470

471

```javascript

472

import { MockAgent, setGlobalDispatcher, MockCallHistory } from 'undici';

473

import { test, beforeEach, afterEach } from 'node:test';

474

import assert from 'node:assert';

475

476

let mockAgent;

477

let callHistory;

478

479

beforeEach(() => {

480

mockAgent = new MockAgent();

481

callHistory = new MockCallHistory();

482

setGlobalDispatcher(mockAgent);

483

484

// Disable real network connections in tests

485

mockAgent.disableNetConnect();

486

});

487

488

afterEach(async () => {

489

await mockAgent.close();

490

});

491

492

test('user service integration', async () => {

493

const mockPool = mockAgent.get('https://api.example.com');

494

495

// Mock user creation

496

mockPool.intercept({

497

path: '/users',

498

method: 'POST',

499

body: (body) => {

500

const data = JSON.parse(body);

501

return data.name && data.email;

502

}

503

}).reply(201, { id: 123, name: 'Alice', email: 'alice@example.com' });

504

505

// Mock user retrieval

506

mockPool.intercept({

507

path: '/users/123',

508

method: 'GET'

509

}).reply(200, { id: 123, name: 'Alice', email: 'alice@example.com' });

510

511

// Test the actual service

512

const createResponse = await fetch('https://api.example.com/users', {

513

method: 'POST',

514

headers: { 'content-type': 'application/json' },

515

body: JSON.stringify({ name: 'Alice', email: 'alice@example.com' })

516

});

517

518

assert.strictEqual(createResponse.status, 201);

519

520

const getResponse = await fetch('https://api.example.com/users/123');

521

const userData = await getResponse.json();

522

523

assert.strictEqual(userData.name, 'Alice');

524

assert.strictEqual(userData.email, 'alice@example.com');

525

526

// Verify all mocks were called

527

mockAgent.assertNoPendingInterceptors();

528

});

529

```