Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
298 changes: 298 additions & 0 deletions docs/docs/api/MockAgent.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,16 @@ Extends: [`AgentOptions`](/docs/docs/api/Agent.md#parameter-agentoptions)

* **acceptNonStandardSearchParameters** `boolean` (optional) - Default: `false` - set to `true` if the matcher should also accept non standard search parameters such as multi-value items specified with `[]` (e.g. `param[]=1&param[]=2&param[]=3`) and multi-value items which values are comma separated (e.g. `param=1,2,3`).

* **enableCallHistory** `boolean` (optional) - Default: `false` - set to `true` to enable tracking all requests made through the MockAgent.

* **traceRequests** `boolean | 'verbose'` (optional) - Default: `false` - enable real-time request tracing to console. Use `'verbose'` for detailed tracing including headers and bodies.

* **developmentMode** `boolean` (optional) - Default: `false` - enable development mode which automatically enables `traceRequests`, `verboseErrors`, and `enableCallHistory`.

* **verboseErrors** `boolean` (optional) - Default: `false` - enable enhanced error messages with context and available interceptors when requests fail to match.

* **console** `{ error: Function }` (optional) - Default: `global console` - custom console implementation for tracing output. Must have an `error` method.

### Example - Basic MockAgent instantiation

This will instantiate the MockAgent. It will not do anything until registered as the agent to use with requests and mock interceptions are added.
Expand All @@ -32,6 +42,23 @@ import { MockAgent } from 'undici'
const mockAgent = new MockAgent()
```

### Example - MockAgent with debugging features

```js
import { MockAgent } from 'undici'

// Enable all debugging features for development
const mockAgent = new MockAgent({ developmentMode: true })

// Or configure individual debugging features
const mockAgent = new MockAgent({
traceRequests: 'verbose', // Detailed request tracing
verboseErrors: true, // Enhanced error messages
enableCallHistory: true, // Track all requests
console: customLogger // Custom console for testing
})
```

### Example - Basic MockAgent instantiation with custom agent

```js
Expand Down Expand Up @@ -522,6 +549,14 @@ This method throws if the mock agent has any pending interceptors. A pending int
- Is persistent (i.e., registered with `.persist()`) and has not been invoked;
- Is registered with `.times(<number>)` and has not been invoked `<number>` of times.

Arguments:

* **options** `AssertNoPendingInterceptorsOptions` (optional)
* **pendingInterceptorsFormatter** `PendingInterceptorsFormatter` (optional) - Custom formatter for pending interceptors
* **showUnusedInterceptors** `boolean` (optional) - Default: `false` - Include interceptors that were never invoked
* **showCallHistory** `boolean` (optional) - Default: `false` - Include recent request history (requires call history to be enabled)
* **includeRequestDiff** `boolean` (optional) - Default: `false` - Compare recent requests against pending interceptors

#### Example - Check that there are no pending interceptors

```js
Expand Down Expand Up @@ -601,3 +636,266 @@ mockAgentHistory?.filterCalls('application/json') // returns an Array of MockCal
mockAgentHistory?.filterCalls((log) => log.path === '/endpoint') // returns an Array of MockCallHistoryLogs when given function returns true
mockAgentHistory?.clear() // clear the history
```

### `MockAgent.debug()`

Returns comprehensive debugging information about the MockAgent state including origins, interceptors, options, and call history.

Returns: `MockAgentDebugInfo`

```js
const mockAgent = new MockAgent({ enableCallHistory: true })
const mockPool = mockAgent.get('http://localhost:3000')
mockPool.intercept({ path: '/users', method: 'GET' }).reply(200, [])

const debugInfo = mockAgent.debug()
console.log(debugInfo)
// {
// origins: ['http://localhost:3000'],
// totalInterceptors: 1,
// pendingInterceptors: 1,
// callHistory: { enabled: true, calls: [] },
// interceptorsByOrigin: {
// 'http://localhost:3000': [{
// method: 'GET',
// path: '/users',
// statusCode: 200,
// timesInvoked: 0,
// persist: false,
// pending: true
// }]
// },
// options: {
// traceRequests: false,
// developmentMode: false,
// verboseErrors: false,
// enableCallHistory: true
// },
// isMockActive: true,
// netConnect: true
// }
```

### `MockAgent.inspect()`

Prints formatted debugging information to console and returns the debug info object. Useful for visual debugging during development.

Returns: `MockAgentDebugInfo`

```js
const mockAgent = new MockAgent()
const mockPool = mockAgent.get('http://localhost:3000')
mockPool.intercept({ path: '/users', method: 'GET' }).reply(200, [])

mockAgent.inspect()
// Console output:
// === MockAgent Debug Information ===
// Mock Active: true
// Net Connect: true
// Total Origins: 1
// Total Interceptors: 1
// Pending Interceptors: 1
// Call History: disabled
//
// Interceptors by Origin:
//
// http://localhost:3000 (1 interceptors):
// β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
// β”‚ Method β”‚ Path β”‚ Status β”‚ Persistent β”‚ Invocations β”‚ Remaining β”‚
// β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
// β”‚ GET β”‚ /users β”‚ 200 β”‚ ❌ β”‚ 0 β”‚ 1 β”‚
// β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
//
// ⚠️ 1 pending interceptors
// === End Debug Information ===
```

### `MockAgent.compareRequest(request, interceptor)`

Compares a request against an interceptor and returns detailed differences including similarity scores. Useful for understanding why requests don't match interceptors.

Arguments:

* **request** `Object` - Request object with `method`, `path`, `body`, `headers` properties
* **interceptor** `Object` - Interceptor object with matching properties

Returns: `ComparisonResult`

```js
const mockAgent = new MockAgent()
const request = { method: 'GET', path: '/api/user', body: undefined }
const interceptor = { method: 'GET', path: '/api/users', body: undefined }

const comparison = mockAgent.compareRequest(request, interceptor)
console.log(comparison)
// {
// matches: false,
// differences: [
// {
// field: 'path',
// expected: '/api/users',
// actual: '/api/user',
// similarity: 0.95
// }
// ],
// score: 0.95
// }
```

## Debugging Features

### Request Tracing

Enable real-time request tracing to see all HTTP requests as they happen:

#### Basic Tracing

```js
const mockAgent = new MockAgent({ traceRequests: true })
setGlobalDispatcher(mockAgent)
mockAgent.disableNetConnect()

const mockPool = mockAgent.get('http://localhost:3000')
mockPool.intercept({ path: '/users', method: 'GET' }).reply(200, [])

// Making requests will output to console:
// [MOCK] Incoming request: GET http://localhost:3000/users
// [MOCK] βœ… MATCHED interceptor: GET /users -> 200

// Failed requests show available interceptors:
// [MOCK] Incoming request: GET http://localhost:3000/posts
// [MOCK] ❌ NO MATCH found for: GET /posts
// [MOCK] Available interceptors:
// - GET /users (path mismatch, similarity: 0.7)
```

#### Verbose Tracing

```js
const mockAgent = new MockAgent({ traceRequests: 'verbose' })
setGlobalDispatcher(mockAgent)
mockAgent.disableNetConnect()

const mockPool = mockAgent.get('http://localhost:3000')
mockPool.intercept({ path: '/users', method: 'POST' }).reply(201, { id: 1 })

// Detailed console output:
// [MOCK] πŸ” Request received:
// Method: POST
// URL: http://localhost:3000/users
// Headers: {"content-type": "application/json"}
// Body: {"name": "John"}
//
// [MOCK] πŸ”Ž Checking interceptors for origin 'http://localhost:3000':
// 1. Testing POST /users... βœ… MATCH!
// - Method: βœ… POST === POST
// - Path: βœ… /users === /users
//
// [MOCK] βœ… Responding with:
// Status: 201
// Headers: {"content-type": "application/json"}
// Body: {"id": 1}
```

### Custom Console for Testing

Redirect tracing output for testing or custom logging:

```js
const mockLogger = {
messages: [],
error(...args) {
this.messages.push(args.join(' '))
// Send to your logging system
myLogger.debug(`[MOCK] ${args.join(' ')}`)
}
}

const mockAgent = new MockAgent({
traceRequests: true,
console: mockLogger
})

// Later in tests:
assert(mockLogger.messages.some(msg => msg.includes('MATCHED')))
assert.equal(mockLogger.messages.filter(msg => msg.includes('Incoming request')).length, 3)
```

### Enhanced Error Messages

Enable verbose error messages with context when requests fail to match:

```js
const mockAgent = new MockAgent({ verboseErrors: true })
setGlobalDispatcher(mockAgent)
mockAgent.disableNetConnect()

const mockPool = mockAgent.get('http://localhost:3000')
mockPool.intercept({ path: '/api/users', method: 'GET' }).reply(200, [])

try {
await request('http://localhost:3000/api/user')
} catch (error) {
console.log(error.message)
// Enhanced error shows available interceptors and suggestions:
// Mock dispatch not matched for path '/api/user'
//
// Available interceptors for origin 'http://localhost:3000':
// β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
// β”‚ Method β”‚ Path β”‚ Status β”‚ Persistent β”‚ Remaining β”‚
// β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
// β”‚ GET β”‚ /api/usersβ”‚ 200 β”‚ ❌ β”‚ 1 β”‚
// β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
//
// Request details:
// - Method: GET
// - Path: /api/user
//
// Potential matches:
// - GET /api/users (close match: path similarity 0.95)
}
```

### Development Mode

Enable all debugging features at once:

```js
const mockAgent = new MockAgent({ developmentMode: true })
// Automatically enables:
// - traceRequests: true
// - verboseErrors: true
// - enableCallHistory: true
```

### Enhanced Test Assertions

Get detailed information when tests fail:

```js
const mockAgent = new MockAgent({ enableCallHistory: true })
setGlobalDispatcher(mockAgent)
mockAgent.disableNetConnect()

const mockPool = mockAgent.get('http://localhost:3000')
mockPool.intercept({ path: '/users', method: 'GET' }).reply(200, [])
mockPool.intercept({ path: '/posts', method: 'GET' }).reply(200, [])

// Make some requests...
await request('http://localhost:3000/users')

try {
mockAgent.assertNoPendingInterceptors({
showUnusedInterceptors: true, // Show interceptors never called
showCallHistory: true, // Show recent requests
includeRequestDiff: true // Compare requests vs interceptors
})
} catch (error) {
console.log(error.message)
// Enhanced output shows:
// - Pending interceptors table
// - Unused interceptors that were never called
// - Recent request history
// - Comparison of recent requests vs pending interceptors
}
```
Loading
Loading