# MockPool > 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` * Extends: {Pool} Extends [`Pool`][] and implements the `Interceptable` interface, allowing requests made through it to be matched against registered mocks. ### `new MockPool(origin[, options])` * `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)` * `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()` * 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)` * `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])` * `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()` * 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