Skip to content

Commit

Permalink
docs: update documentation
Browse files Browse the repository at this point in the history
  • Loading branch information
blephy committed Jan 27, 2025
1 parent a0e046b commit 4a13b93
Show file tree
Hide file tree
Showing 3 changed files with 204 additions and 1 deletion.
124 changes: 124 additions & 0 deletions docs/docs/api/MockAgent.md
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,9 @@ for await (const data of result2.body) {
console.log('data', data.toString('utf8')) // data hello
}
```

#### Example - Mock different requests within the same file

```js
const { MockAgent, setGlobalDispatcher } = require('undici');
const agent = new MockAgent();
Expand Down Expand Up @@ -540,3 +542,125 @@ agent.assertNoPendingInterceptors()
// │ 0 │ 'GET' │ 'https://example.com' │ '/' │ 200 │ '❌' │ 0 │ 1 │
// └─────────┴────────┴───────────────────────┴──────┴─────────────┴────────────┴─────────────┴───────────┘
```

#### Example - access call history on MockAgent

By default, every call made within a MockAgent have their request configuration historied

```js
import { MockAgent, setGlobalDispatcher, request } from 'undici'

const mockAgent = new MockAgent()
setGlobalDispatcher(mockAgent)

await request('http://example.com', { query: { item: 1 }})

mockAgent.getCallHistory().firstCall()
// Returns
// MockCallHistoryLog {
// body: undefined,
// headers: undefined,
// method: 'GET',
// origin: 'http://example.com',
// fullUrl: 'http://example.com/?item=1',
// path: '/',
// searchParams: { item: '1' },
// protocol: 'http:',
// host: 'example.com',
// port: ''
// }
```

#### Example - access call history on intercepted client

You can use `registerCallHistory` to register a specific MockCallHistory instance while you are intercepting request. This is useful to have an history already filtered. Note that `getCallHistory()` will still register every request configuration.

```js
import { MockAgent, setGlobalDispatcher, request } from 'undici'

const mockAgent = new MockAgent()
setGlobalDispatcher(mockAgent)

const client = mockAgent.get('http://example.com')

client.intercept({ path: '/', method: 'GET' }).reply(200, 'hi !').registerCallHistory('my-specific-history-name')

await request('http://example.com') // intercepted
await request('http://example.com', { method: 'POST', body: JSON.stringify({ data: 'hello' }), headers: { 'content-type': 'application/json' }})

mockAgent.getCallHistory('my-specific-history-name').calls()
// Returns [
// MockCallHistoryLog {
// body: undefined,
// headers: undefined,
// method: 'GET',
// origin: 'http://example.com',
// fullUrl: 'http://example.com/',
// path: '/',
// searchParams: {},
// protocol: 'http:',
// host: 'example.com',
// port: ''
// }
// ]

mockAgent.getCallHistory().calls()
// Returns [
// MockCallHistoryLog {
// body: undefined,
// headers: undefined,
// method: 'GET',
// origin: 'http://example.com',
// fullUrl: 'http://example.com/',
// path: '/',
// searchParams: {},
// protocol: 'http:',
// host: 'example.com',
// port: ''
// },
// MockCallHistoryLog {
// body: "{ "data": "hello" }",
// headers: { 'content-type': 'application/json' },
// method: 'POST',
// origin: 'http://example.com',
// fullUrl: 'http://example.com/',
// path: '/',
// searchParams: {},
// protocol: 'http:',
// host: 'example.com',
// port: ''
// }
// ]
```

#### Example - clear call history

Clear all call history registered :

```js
const mockAgent = new MockAgent()

mockAgent.clearAllCallHistory()
```

Clear only one call history :

```js
const mockAgent = new MockAgent()

mockAgent.getCallHistory().clear()
mockAgent.getCallHistory('my-history')?.clear()
```
#### Example - call history instance class method
```js
const mockAgent = new MockAgent()

const mockAgentHistory = mockAgent.getCallHistory()

mockAgentHistory.calls() // returns an array of MockCallHistoryLogs
mockAgentHistory.firstCall() // returns the first MockCallHistoryLogs or undefined
mockAgentHistory.lastCall() // returns the last MockCallHistoryLogs or undefined
mockAgentHistory.nthCall(3) // returns the third MockCallHistoryLogs or undefined
```
67 changes: 67 additions & 0 deletions docs/docs/best-practices/mocking-request.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,73 @@ assert.deepEqual(badRequest, { message: 'bank account not found' })

Explore other MockAgent functionality [here](/docs/docs/api/MockAgent.md)

## Access agent history

Using a MockAgent also allows you to make assertions on the configuration used to make your http calls in your application.

Here is an example :

```js
// index.test.mjs
import { strict as assert } from 'assert'
import { MockAgent, setGlobalDispatcher, fetch } from 'undici'
import { app } from './app.mjs'

// given an application server running on http://localhost:3000
await app.start()

const mockAgent = new MockAgent()

setGlobalDispatcher(mockAgent)

// this call is made (not intercepted)
await fetch(`http://localhost:3000/endpoint?query='hello'`, {
method: 'POST',
headers: { 'content-type': 'application/json' }
body: JSON.stringify({ data: '' })
})

// access to the call history of the MockAgent (which register every call made intercepted or not)
assert.ok(mockAgent.getCallHistory().calls().length === 1)
assert.strictEqual(mockAgent.getCallHistory().firstCall()?.fullUrl, `http://localhost:3000/endpoint?query='hello'`)
assert.strictEqual(mockAgent.getCallHistory().firstCall()?.body, JSON.stringify({ data: '' }))
assert.deepStrictEqual(mockAgent.getCallHistory().firstCall()?.searchParams, { query: 'hello' })
assert.strictEqual(mockAgent.getCallHistory().firstCall()?.port, '3000')
assert.strictEqual(mockAgent.getCallHistory().firstCall()?.host, 'localhost:3000')
assert.strictEqual(mockAgent.getCallHistory().firstCall()?.method, 'POST')
assert.strictEqual(mockAgent.getCallHistory().firstCall()?.path, '/endpoint')
assert.deepStrictEqual(mockAgent.getCallHistory().firstCall()?.headers, { 'content-type': 'application/json' })

// register a specific call history for a given interceptor (useful to filter call within a particular interceptor)
const mockPool = mockAgent.get('http://localhost:3000');

// we intercept a call and we register a specific MockCallHistory
mockPool.intercept({
path: '/second-endpoint',
}).reply(200, 'hello').registerCallHistory('second-endpoint-history')

assert.ok(mockAgent.getCallHistory().calls().length === 2) // MockAgent call history has registered the call too
assert.ok(mockAgent.getCallHistory('second-endpoint-history')?.calls().length === 1)
assert.strictEqual(mockAgent.getCallHistory('second-endpoint-history')?.firstCall()?.path, '/second-endpoint')
assert.strictEqual(mockAgent.getCallHistory('second-endpoint-history')?.firstCall()?.method, 'GET')

// clearing all call history

mockAgent.clearAllCallHistory()

assert.ok(mockAgent.getCallHistory().calls().length === 0)
assert.ok(mockAgent.getCallHistory('second-endpoint-history')?.calls().length === 0)

// clearing a particular history

mockAgent.getCallHistory().clear() // second-endpoint-history will not be cleared
mockAgent.getCallHistory('second-endpoint-history').clear() // it is not cleared
```
Calling `mockAgent.close()` will automatically clear and delete every call history for you.
Explore other MockAgent functionality [here](/docs/docs/api/MockAgent.md)
## Debug Mock Value
When the interceptor and the request options are not the same, undici will automatically make a real HTTP request. To prevent real requests from being made, use `mockAgent.disableNetConnect()`:
Expand Down
14 changes: 13 additions & 1 deletion lib/mock/mock-call-history.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ const {
kMockCallHistoryClearAll,
kMockCallHistoryDeleteAll
} = require('./mock-symbols')
const { InvalidArgumentError } = require('../core/errors')

const computingError = 'error occurred when computing MockCallHistoryLog.url'

Expand Down Expand Up @@ -96,7 +97,18 @@ class MockCallHistory {
}

nthCall (number) {
return this.logs.at(number)
if (typeof number !== 'number') {
throw new InvalidArgumentError('nthCall must be called with a number')
}
if (!Number.isInteger(number)) {
throw new InvalidArgumentError('nthCall must be called with an integer')
}
if (Math.sign(number) !== 1) {
throw new InvalidArgumentError('nthCall must be called with a positive value. use firstCall or lastCall instead')
}

// non zero based index. this is more human readable
return this.logs.at(number - 1)
}

clear () {
Expand Down

0 comments on commit 4a13b93

Please sign in to comment.