Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add mock call history to access request configuration in test #4029

Open
wants to merge 22 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 19 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
134 changes: 134 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,135 @@ agent.assertNoPendingInterceptors()
// │ 0 │ 'GET' │ 'https://example.com' │ '/' │ 200 │ '❌' │ 0 │ 1 │
// └─────────┴────────┴───────────────────────┴──────┴─────────────┴────────────┴─────────────┴───────────┘
```

#### Example - access call history on MockAgent

You can register every call made within a MockAgent to be able to retrieve the body, headers and so on.

This is not enabled by default.

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

const mockAgent = new MockAgent({ enableCallHistory: true })
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 if you previously enable call history.

> using registerCallHistory with a disabled MockAgent will still register an history on the intercepted request.

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

const mockAgent = new MockAgent({ enableCallHistory: true })
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
mockAgentHistory?.filterCalls({ path: '/endpoint', hash: '#hash-value' }) // returns an Array of MockCallHistoryLogs WHERE path === /endpoint OR hash === #hash-value
mockAgentHistory?.filterCalls({ path: '/endpoint', hash: '#hash-value' }, { operator: 'AND' }) // returns an Array of MockCallHistoryLogs WHERE path === /endpoint AND hash === #hash-value
mockAgentHistory?.filterCalls(/"data": "{}"/) // returns an Array of MockCallHistoryLogs where any value match regexp
mockAgentHistory?.filterCalls('application/json') // returns an Array of MockCallHistoryLogs where any value === 'application/json'
mockAgentHistory?.filterCalls((log) => log.path === '/endpoint') // returns an Array of MockCallHistoryLogs when given function returns true
mockAgentHistory?.clear() // clear the history
```
195 changes: 195 additions & 0 deletions docs/docs/api/MockCallHistory.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
# Class: MockCallHistory

Access to an instance with :

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

// or
const mockAgent = new MockAgent()
mockAgent.enableMockHistory()
mockAgent.getCallHistory()

// or
const mockAgent = new MockAgent()
const mockClient = mockAgent.get('http://localhost:3000')
mockClient
.intercept({ path: '/' })
.reply(200, 'hello')
.registerCallHistory('my-custom-history')
mockAgent.getCallHistory('my-custom-history')
```

## class methods

### clear

Clear all MockCallHistoryLog registered

```js
mockAgent.getCallHistory()?.clear() // clear only mockAgent history
mockAgent.getCallHistory('my-custom-history')?.clear() // clear only 'my-custom-history' history
```

> to clear all registered MockCallHistory, use `mockAgent.clearAllCallHistory()`. This is automatically done when calling `mockAgent.close()`

### calls

Get all MockCallHistoryLog registered as an array

```js
mockAgent.getCallHistory()?.calls()
```

### firstCall

Get the first MockCallHistoryLog registered or undefined

```js
mockAgent.getCallHistory()?.firstCall()
```

### lastCall

Get the last MockCallHistoryLog registered or undefined

```js
mockAgent.getCallHistory()?.lastCall()
```

### nthCall

Get the nth MockCallHistoryLog registered or undefined

```js
mockAgent.getCallHistory()?.nthCall(3) // the third MockCallHistoryLog registered
```

### filterCallsByProtocol

Filter MockCallHistoryLog by protocol.

> more details for the first parameter can be found [here](/docs/docs/api/MockCallHistory.md#filter-parameter)

```js
mockAgent.getCallHistory()?.filterCallsByProtocol(/https/)
mockAgent.getCallHistory()?.filterCallsByProtocol('https:')
```

### filterCallsByHost

Filter MockCallHistoryLog by host.

> more details for the first parameter can be found [here](/docs/docs/api/MockCallHistory.md#filter-parameter)

```js
mockAgent.getCallHistory()?.filterCallsByHost(/localhost/)
mockAgent.getCallHistory()?.filterCallsByHost('localhost:3000')
```

### filterCallsByPort

Filter MockCallHistoryLog by port.

> more details for the first parameter can be found [here](/docs/docs/api/MockCallHistory.md#filter-parameter)

```js
mockAgent.getCallHistory()?.filterCallsByPort(/3000/)
mockAgent.getCallHistory()?.filterCallsByPort('3000')
mockAgent.getCallHistory()?.filterCallsByPort('')
```

### filterCallsByOrigin

Filter MockCallHistoryLog by origin.

> more details for the first parameter can be found [here](/docs/docs/api/MockCallHistory.md#filter-parameter)

```js
mockAgent.getCallHistory()?.filterCallsByOrigin(/http:\/\/localhost:3000/)
mockAgent.getCallHistory()?.filterCallsByOrigin('http://localhost:3000')
```

### filterCallsByPath

Filter MockCallHistoryLog by path.

> more details for the first parameter can be found [here](/docs/docs/api/MockCallHistory.md#filter-parameter)

```js
mockAgent.getCallHistory()?.filterCallsByPath(/api\/v1\/graphql/)
mockAgent.getCallHistory()?.filterCallsByPath('/api/v1/graphql')
```

### filterCallsByHash

Filter MockCallHistoryLog by hash.

> more details for the first parameter can be found [here](/docs/docs/api/MockCallHistory.md#filter-parameter)

```js
mockAgent.getCallHistory()?.filterCallsByPath(/hash/)
mockAgent.getCallHistory()?.filterCallsByPath('#hash')
```

### filterCallsByFullUrl

Filter MockCallHistoryLog by fullUrl. fullUrl contains protocol, host, port, path, hash, and query params

> more details for the first parameter can be found [here](/docs/docs/api/MockCallHistory.md#filter-parameter)

```js
mockAgent.getCallHistory()?.filterCallsByFullUrl(/https:\/\/localhost:3000\/\?query=value#hash/)
mockAgent.getCallHistory()?.filterCallsByFullUrl('https://localhost:3000/?query=value#hash')
```

### filterCallsByMethod

Filter MockCallHistoryLog by method.

> more details for the first parameter can be found [here](/docs/docs/api/MockCallHistory.md#filter-parameter)

```js
mockAgent.getCallHistory()?.filterCallsByMethod(/POST/)
mockAgent.getCallHistory()?.filterCallsByMethod('POST')
```

### filterCalls

This class method is a meta function / alias to apply complex filtering in a single way.

Parameters :

- criteria : the first parameter. a function, regexp or object.
- function : filter MockCallHistoryLog when the function returns false
- regexp : filter MockCallHistoryLog when the regexp does not match on MockCallHistoryLog.toString() ([see](./MockCallHistoryLog.md#to-string))
- object : an object with MockCallHistoryLog properties as keys to apply multiple filters. each values are a [filter parameter](/docs/docs/api/MockCallHistory.md#filter-parameter)
- options : the second parameter. an object.
- options.operator : `'AND'` or `'OR'` (default `'OR'`). Used only if criteria is an object. see below

```js
mockAgent.getCallHistory()?.filterCalls((log) => log.hash === value && log.headers?.['authorization'] !== undefined)
mockAgent.getCallHistory()?.filterCalls(/"data": "{ "errors": "wrong body" }"/)

// returns an Array of MockCallHistoryLog which all have
// - a hash containing my-hash
// - OR
// - a path equal to /endpoint
mockAgent.getCallHistory()?.filterCalls({ hash: /my-hash/, path: '/endpoint' })

// returns an Array of MockCallHistoryLog which all have
// - a hash containing my-hash
// - AND
// - a path equal to /endpoint
mockAgent.getCallHistory()?.filterCalls({ hash: /my-hash/, path: '/endpoint' }, { operator: 'AND' })
```

## filter parameter

Can be :

- string. MockCallHistoryLog filtered if `value !== parameterValue`
- null. MockCallHistoryLog filtered if `value !== parameterValue`
- undefined. MockCallHistoryLog filtered if `value !== parameterValue`
- regexp. MockCallHistoryLog filtered if `!parameterValue.test(value)`
43 changes: 43 additions & 0 deletions docs/docs/api/MockCallHistoryLog.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# Class: MockCallHistoryLog

Access to an instance with :

```js
const mockAgent = new MockAgent({ enableCallHistory: true })
mockAgent.getCallHistory()?.firstCall()
```

## class properties

- body `mockAgent.getCallHistory()?.firstCall()?.body`
- headers `mockAgent.getCallHistory()?.firstCall()?.headers` an object
- method `mockAgent.getCallHistory()?.firstCall()?.method` a string
- fullUrl `mockAgent.getCallHistory()?.firstCall()?.fullUrl` a string containing the protocol, origin, path, query and hash
- origin `mockAgent.getCallHistory()?.firstCall()?.origin` a string containing the protocol and the host
- headers `mockAgent.getCallHistory()?.firstCall()?.headers` an object
- path `mockAgent.getCallHistory()?.firstCall()?.path` a string always starting with `/`
- searchParams `mockAgent.getCallHistory()?.firstCall()?.searchParams` an object
- protocol `mockAgent.getCallHistory()?.firstCall()?.protocol` a string (`https:`)
- host `mockAgent.getCallHistory()?.firstCall()?.host` a string
- port `mockAgent.getCallHistory()?.firstCall()?.port` an empty string or a string containing numbers
- hash `mockAgent.getCallHistory()?.firstCall()?.hash` an empty string or a string starting with `#`

## class methods

### toMap

Returns a Map instance

```js
mockAgent.getCallHistory()?.firstCall()?.toMap()?.get('hash')
// #hash
```

### toString

Returns a string computed with any class property name and value pair

```js
mockAgent.getCallHistory()?.firstCall()?.toString()
// protocol->https:|host->localhost:4000|port->4000|origin->https://localhost:4000|path->/endpoint|hash->#here|searchParams->{"query":"value"}|fullUrl->https://localhost:4000/endpoint?query=value#here|method->PUT|body->"{ "data": "hello" }"|headers->{"content-type":"application/json"}
```
Loading
Loading