Files
SnowLuma/.pnpm-store/v11/files/1b/3729dd2050a7f7c46a1b0ccde54f62c7d9c8a8a653597a74b517e42ed44aaabd5d9b2e75fd25fcc370db587139f4f1f5f3293dc3fe20f9d479e62d1f3b6bfe
TanTan 2a69b23b47
Dev Build / cleanup (push) Waiting to run
Dev Build / typecheck (push) Waiting to run
docs: 仓库链接指向本镜像仓库
2026-11-13 09:27:07 +08:00

619 lines
19 KiB
Plaintext

# MockPool
<!--introduced_in=v4.0.0-->
<!--type=module-->
<!-- source_link=lib/mock/mock-pool.js -->
> Stability: 2 - Stable
A `MockPool` is a [`Pool`][] that intercepts requests matching registered routes
and replies with mocked responses instead of contacting the network. It is
created by a [`MockAgent`][] and exposes the same interception API as
[`MockClient`][].
A `MockPool` is not constructed directly in most cases; instead it is obtained
through [`mockAgent.get(origin)`][]. Once registered as the global dispatcher,
any request whose origin matches the pool is matched against the mocks it holds.
```mjs
import { MockAgent } from 'undici'
const mockAgent = new MockAgent()
const mockPool = mockAgent.get('http://localhost:3000')
```
```cjs
const { MockAgent } = require('undici')
const mockAgent = new MockAgent()
const mockPool = mockAgent.get('http://localhost:3000')
```
## Class: `MockPool`
<!-- YAML
added: v4.0.0
-->
* Extends: {Pool}
Extends [`Pool`][] and implements the `Interceptable` interface, allowing
requests made through it to be matched against registered mocks.
### `new MockPool(origin[, options])`
<!-- YAML
added: v4.0.0
-->
* `origin` {string} The origin to associate this mock pool with. It should only
include the protocol, hostname, and port.
* `options` {MockPoolOptions} Extends the [`Pool`][] options.
* Returns: {MockPool}
The `agent` option is required and must implement the `Agent` interface;
otherwise an `InvalidArgumentError` is thrown.
#### Parameter: `MockPoolOptions`
Extends: `PoolOptions`
* `agent` {MockAgent} The agent to associate this mock pool with.
* `ignoreTrailingSlash` {boolean} Whether trailing slashes should be ignored
when matching the path of intercepted requests. **Default:** `false`.
### `mockPool.intercept(options)`
<!-- YAML
added: v4.0.0
-->
* `options` {MockPoolInterceptOptions} The matching criteria for the requests to
intercept.
* Returns: {MockInterceptor} The interceptor used to define the mocked reply.
Registers a route on the mock pool and returns a [`MockInterceptor`][] for any
matching request that uses the same origin as this mock pool. The interceptor is
then used to define the reply, for example with `reply()` or `replyWithError()`.
Each `intercept()` call is consumed by a single matching request. To match more
than one request, call `intercept()` once per expected request, or use
`persist()` or `times()` on the returned [`MockScope`][]. When no registered
mock matches a request, a real request is attempted unless network connections
have been disabled on the [`MockAgent`][], in which case a `MockNotMatchedError`
is thrown.
A request is intercepted only when every defined matcher passes. The accepted
matcher forms behave as follows:
| Matcher type | Condition to pass |
| :----------: | ---------------------------- |
| `string` | Exact match against the value |
| `RegExp` | The expression must match |
| `Function` | The function must return `true` |
#### Parameter: `MockPoolInterceptOptions`
* `path` {string|RegExp|Function} A matcher for the HTTP request path. The
function form has the signature `(path: string) => boolean`. When a `RegExp`
or function is used, it matches against the request path including all query
parameters in alphabetical order. When a `string` is used, query parameters
can instead be supplied through `query`.
* `method` {string|RegExp|Function} A matcher for the HTTP request method. The
function form has the signature `(method: string) => boolean`.
**Default:** `'GET'`.
* `body` {string|RegExp|Function} A matcher for the HTTP request body. The
function form has the signature `(body: string) => boolean`.
* `headers` {Object|Function} A matcher for the HTTP request headers, given
either as a map of header name to a `string`, `RegExp`, or
`(value: string) => boolean` matcher, or as a single function that receives
all headers and returns a `boolean`. When a map is used, the request must
match every defined header; extra headers not listed here do not affect
matching.
* `query` {Object} A matcher for the HTTP request query string parameters. Only
applies when a `string` was provided for `path`.
* `ignoreTrailingSlash` {boolean} Whether a trailing slash on `path` is ignored
when matching. Inherited from the mock pool's `ignoreTrailingSlash` option
when not set.
#### Return: `MockInterceptor`
The reply behaviour of a matching request is defined through the returned
`MockInterceptor`.
* `reply(statusCode[, data[, responseOptions]])` {Function} Defines the reply
for a matching request.
* `statusCode` {number} The status code of the mocked reply.
* `data` {string|Buffer|Object|Function} The body of the mocked reply.
Objects are serialized to JSON; strings and `Buffer`s are sent as-is. The
function form has the signature
`(opts: MockResponseCallbackOptions) => string | Buffer | Object` and is
invoked with the incoming request to compute the reply body.
* `responseOptions` {MockResponseOptions} Additional reply options.
**Default:** `{}`.
* Returns: {MockScope}
* `reply(callback)` {Function} Defines the reply for a matching request,
computing all reply options dynamically rather than just the body.
* `callback` {Function} A `(opts: MockResponseCallbackOptions) =>
{ statusCode, data, responseOptions }` function invoked with the incoming
request. The callback may be asynchronous; a returned promise is awaited
and must resolve to the same shape.
* Returns: {MockScope}
* `replyWithError(error)` {Function} Defines an error for a matching request to
throw.
* `error` {Error} The error thrown when a request matches.
* Returns: {MockScope}
* `defaultReplyHeaders(headers)` {Function} Sets default headers included on
every subsequent reply defined on this interceptor, in addition to any headers
set on a specific reply.
* `headers` {Object} A map of header name to value.
* Returns: {MockInterceptor}
* `defaultReplyTrailers(trailers)` {Function} Sets default trailers included on
every subsequent reply defined on this interceptor, in addition to any
trailers set on a specific reply.
* `trailers` {Object} A map of trailer name to value.
* Returns: {MockInterceptor}
* `replyContentLength()` {Function} Sets an automatically calculated
`content-length` header on every subsequent reply defined on this interceptor.
* Returns: {MockInterceptor}
By default, `reply()` and `replyWithError()` define the behaviour for the first
matching request only; subsequent requests are not affected unless `persist()`
or `times()` is used on the returned [`MockScope`][].
#### Parameter: `MockResponseCallbackOptions`
The argument passed to the `reply()` data and options callbacks.
* `path` {string} The path of the intercepted request.
* `method` {string} The method of the intercepted request.
* `headers` {Object|Headers} The headers of the intercepted request.
* `origin` {string} The origin of the intercepted request.
* `body` {string|null} The body of the intercepted request.
#### Parameter: `MockResponseOptions`
* `headers` {Object} Headers to include on the mocked reply.
* `trailers` {Object} Trailers to include on the mocked reply.
#### Return: `MockScope`
A `MockScope` is associated with a single [`MockInterceptor`][] and configures
how many times the defined reply is used.
* `delay(waitInMs)` {Function} Delays the associated reply by a set amount of
time.
* `waitInMs` {number} The delay in milliseconds.
* Returns: {MockScope}
* `persist()` {Function} Makes the associated reply match indefinitely, so every
matching request receives the defined response.
* Returns: {MockScope}
* `times(repeatTimes)` {Function} Makes the associated reply match a fixed number
of times. This is overridden by `persist()`.
* `repeatTimes` {number} The number of matching requests the reply applies to.
* Returns: {MockScope}
The following examples show common interception patterns.
```mjs displayName="Basic mocked request"
import { MockAgent, setGlobalDispatcher, request } from 'undici'
const mockAgent = new MockAgent()
setGlobalDispatcher(mockAgent)
const mockPool = mockAgent.get('http://localhost:3000')
mockPool.intercept({ path: '/foo' }).reply(200, 'foo')
const { statusCode, body } = await request('http://localhost:3000/foo')
console.log('response received', statusCode) // response received 200
for await (const data of body) {
console.log('data', data.toString('utf8')) // data foo
}
```
```mjs displayName="Reply with a data callback"
import { MockAgent, setGlobalDispatcher, request } from 'undici'
const mockAgent = new MockAgent()
setGlobalDispatcher(mockAgent)
const mockPool = mockAgent.get('http://localhost:3000')
mockPool.intercept({
path: '/echo',
method: 'GET'
}).reply(200, ({ headers }) => ({ message: headers.message }))
const { statusCode, body } = await request('http://localhost:3000/echo', {
headers: { message: 'hello world!' }
})
console.log('response received', statusCode) // response received 200
for await (const data of body) {
console.log('data', data.toString('utf8')) // {"message":"hello world!"}
}
```
```mjs displayName="Reply with an options callback"
import { MockAgent, setGlobalDispatcher, request } from 'undici'
const mockAgent = new MockAgent()
setGlobalDispatcher(mockAgent)
const mockPool = mockAgent.get('http://localhost:3000')
mockPool.intercept({
path: '/echo',
method: 'GET'
}).reply(({ headers }) => ({
statusCode: 200,
data: { message: headers.message }
}))
const { statusCode, body } = await request('http://localhost:3000/echo', {
headers: { message: 'hello world!' }
})
console.log('response received', statusCode) // response received 200
for await (const data of body) {
console.log('data', data.toString('utf8')) // {"message":"hello world!"}
}
```
```mjs displayName="Reply with an asynchronous options callback"
import { readFile } from 'node:fs/promises'
import { MockAgent, setGlobalDispatcher, request } from 'undici'
const mockAgent = new MockAgent()
setGlobalDispatcher(mockAgent)
const mockPool = mockAgent.get('http://localhost:3000')
mockPool.intercept({
path: '/fixture',
method: 'GET'
}).reply(async ({ path }) => ({
statusCode: 200,
data: await readFile(new URL('./fixture.json', import.meta.url))
}))
const { statusCode, body } = await request('http://localhost:3000/fixture')
console.log('response received', statusCode) // response received 200
for await (const data of body) {
console.log('data', data.toString('utf8')) // contents of fixture.json
}
```
```mjs displayName="Multiple intercepts"
import { MockAgent, setGlobalDispatcher, request } from 'undici'
const mockAgent = new MockAgent()
setGlobalDispatcher(mockAgent)
const mockPool = mockAgent.get('http://localhost:3000')
mockPool.intercept({ path: '/foo', method: 'GET' }).reply(200, 'foo')
mockPool.intercept({ path: '/hello', method: 'GET' }).reply(200, 'hello')
const fooResult = await request('http://localhost:3000/foo')
for await (const data of fooResult.body) {
console.log('data', data.toString('utf8')) // data foo
}
const helloResult = await request('http://localhost:3000/hello')
for await (const data of helloResult.body) {
console.log('data', data.toString('utf8')) // data hello
}
```
```mjs displayName="Match query, body, request headers; reply with headers and trailers"
import { MockAgent, setGlobalDispatcher, request } from 'undici'
const mockAgent = new MockAgent()
setGlobalDispatcher(mockAgent)
const mockPool = mockAgent.get('http://localhost:3000')
mockPool.intercept({
path: '/foo?hello=there&see=ya',
method: 'POST',
body: 'form1=data1&form2=data2',
headers: {
'User-Agent': 'undici',
Host: 'example.com'
}
}).reply(200, { foo: 'bar' }, {
headers: { 'content-type': 'application/json' },
trailers: { 'Content-MD5': 'test' }
})
const { statusCode, headers, trailers, body } = await request(
'http://localhost:3000/foo?hello=there&see=ya',
{
method: 'POST',
body: 'form1=data1&form2=data2',
headers: {
foo: 'bar',
'User-Agent': 'undici',
Host: 'example.com'
}
}
)
console.log('response received', statusCode) // response received 200
console.log('headers', headers) // { 'content-type': 'application/json' }
for await (const data of body) {
console.log('data', data.toString('utf8')) // {"foo":"bar"}
}
console.log('trailers', trailers) // { 'content-md5': 'test' }
```
```mjs displayName="Different matcher forms"
import { MockAgent, setGlobalDispatcher, request } from 'undici'
const mockAgent = new MockAgent()
setGlobalDispatcher(mockAgent)
const mockPool = mockAgent.get('http://localhost:3000')
mockPool.intercept({
path: '/foo',
method: /^GET$/,
body: (value) => value === 'form=data',
headers: {
'User-Agent': 'undici',
Host: /^example\.com$/
}
}).reply(200, 'foo')
const { statusCode, body } = await request('http://localhost:3000/foo', {
method: 'GET',
body: 'form=data',
headers: {
foo: 'bar',
'User-Agent': 'undici',
Host: 'example.com'
}
})
console.log('response received', statusCode) // response received 200
for await (const data of body) {
console.log('data', data.toString('utf8')) // data foo
}
```
```mjs displayName="Reply with an error"
import { MockAgent, setGlobalDispatcher, request } from 'undici'
const mockAgent = new MockAgent()
setGlobalDispatcher(mockAgent)
const mockPool = mockAgent.get('http://localhost:3000')
mockPool.intercept({
path: '/foo',
method: 'GET'
}).replyWithError(new Error('kaboom'))
try {
await request('http://localhost:3000/foo', { method: 'GET' })
} catch (error) {
console.error(error.message) // kaboom
}
```
```mjs displayName="Default reply headers"
import { MockAgent, setGlobalDispatcher, request } from 'undici'
const mockAgent = new MockAgent()
setGlobalDispatcher(mockAgent)
const mockPool = mockAgent.get('http://localhost:3000')
mockPool.intercept({ path: '/foo', method: 'GET' })
.defaultReplyHeaders({ foo: 'bar' })
.reply(200, 'foo')
const { headers } = await request('http://localhost:3000/foo')
console.log('headers', headers) // headers { foo: 'bar' }
```
```mjs displayName="Default reply trailers"
import { MockAgent, setGlobalDispatcher, request } from 'undici'
const mockAgent = new MockAgent()
setGlobalDispatcher(mockAgent)
const mockPool = mockAgent.get('http://localhost:3000')
mockPool.intercept({ path: '/foo', method: 'GET' })
.defaultReplyTrailers({ foo: 'bar' })
.reply(200, 'foo')
const { trailers } = await request('http://localhost:3000/foo')
console.log('trailers', trailers) // trailers { foo: 'bar' }
```
```mjs displayName="Automatic content-length"
import { MockAgent, setGlobalDispatcher, request } from 'undici'
const mockAgent = new MockAgent()
setGlobalDispatcher(mockAgent)
const mockPool = mockAgent.get('http://localhost:3000')
mockPool.intercept({ path: '/foo', method: 'GET' })
.replyContentLength()
.reply(200, 'foo')
const { headers } = await request('http://localhost:3000/foo')
console.log('headers', headers) // headers { 'content-length': '3' }
```
```mjs displayName="Persisted reply"
import { MockAgent, setGlobalDispatcher, request } from 'undici'
const mockAgent = new MockAgent()
setGlobalDispatcher(mockAgent)
const mockPool = mockAgent.get('http://localhost:3000')
mockPool.intercept({ path: '/foo', method: 'GET' }).reply(200, 'foo').persist()
await request('http://localhost:3000/foo') // Matches and returns the mock
await request('http://localhost:3000/foo') // Matches again, indefinitely
```
```mjs displayName="Reply a fixed number of times"
import { MockAgent, setGlobalDispatcher, request } from 'undici'
const mockAgent = new MockAgent()
setGlobalDispatcher(mockAgent)
const mockPool = mockAgent.get('http://localhost:3000')
mockPool.intercept({ path: '/foo', method: 'GET' }).reply(200, 'foo').times(2)
await request('http://localhost:3000/foo') // Matches and returns the mock
await request('http://localhost:3000/foo') // Matches and returns the mock
await request('http://localhost:3000/foo') // No match; a real request is attempted
```
```mjs displayName="Match the path with a function"
import { MockAgent, setGlobalDispatcher, request } from 'undici'
import querystring from 'node:querystring'
const mockAgent = new MockAgent()
setGlobalDispatcher(mockAgent)
const mockPool = mockAgent.get('http://localhost:3000')
const matchPath = (requestPath) => {
const [pathname, search] = requestPath.split('?')
const requestQuery = querystring.parse(search)
if (!pathname.startsWith('/foo')) {
return false
}
return requestQuery.foo === 'bar'
}
mockPool.intercept({ path: matchPath, method: 'GET' }).reply(200, 'foo')
await request('http://localhost:3000/foo?foo=bar') // Matches and returns the mock
```
### `mockPool.close()`
<!-- YAML
added: v4.0.0
-->
* Returns: {Promise} Fulfills with `undefined` once the mock pool is closed.
Closes the mock pool, gracefully waiting for any enqueued requests to complete,
and removes it from the associated [`MockAgent`][].
```mjs
import { MockAgent } from 'undici'
const mockAgent = new MockAgent()
const mockPool = mockAgent.get('http://localhost:3000')
await mockPool.close()
```
### `mockPool.dispatch(options, handlers)`
<!-- YAML
added: v4.0.0
-->
* `options` {DispatchOptions} The request options.
* `handlers` {DispatchHandler} The handlers invoked over the request lifecycle.
* Returns: {boolean} `false` if the dispatcher is busy and the caller should
wait before dispatching further requests, otherwise `true`.
Dispatches a request, matching it against the registered mocks. This override of
[`dispatcher.dispatch(options, handlers)`][] is what drives the mocking
behaviour for every higher-level method such as `request`.
### `mockPool.request(options[, callback])`
<!-- YAML
added: v4.0.0
-->
* `options` {DispatchOptions}
* `callback` {Function} (optional) Invoked with the response when no `Promise`
is requested.
* Returns: {Promise} Fulfills with the mocked response when no `callback` is
provided.
Performs a request and resolves it against the registered mocks. Inherited from
[`Pool`][]; see [`dispatcher.request(options[, callback])`][] for the full
parameter and return value documentation.
```mjs
import { MockAgent } from 'undici'
const mockAgent = new MockAgent()
const mockPool = mockAgent.get('http://localhost:3000')
mockPool.intercept({ path: '/foo', method: 'GET' }).reply(200, 'foo')
const { statusCode, body } = await mockPool.request({
origin: 'http://localhost:3000',
path: '/foo',
method: 'GET'
})
console.log('response received', statusCode) // response received 200
for await (const data of body) {
console.log('data', data.toString('utf8')) // data foo
}
```
### `mockPool.cleanMocks()`
<!-- YAML
added: v7.11.0
-->
* Returns: {undefined}
Removes all registered interceptors from the mock pool. Pending mocks defined
before this call no longer match incoming requests.
```mjs
import { MockAgent } from 'undici'
const mockAgent = new MockAgent()
const mockPool = mockAgent.get('http://localhost:3000')
mockPool.intercept({ path: '/foo' }).reply(200, 'foo')
mockPool.cleanMocks()
```
[`MockAgent`]: MockAgent.md#class-mockagent
[`MockClient`]: MockClient.md#class-mockclient
[`MockInterceptor`]: MockPool.md#return-mockinterceptor
[`MockScope`]: MockPool.md#return-mockscope
[`Pool`]: Pool.md#class-pool
[`dispatcher.dispatch(options, handlers)`]: Dispatcher.md#dispatcherdispatchoptions-handler
[`dispatcher.request(options[, callback])`]: Dispatcher.md#dispatcherrequestoptions-callback
[`mockAgent.get(origin)`]: MockAgent.md#mockagentgetorigin