1# Class: MockPool 2 3Extends: `undici.Pool` 4 5A mock Pool class that implements the Pool API and is used by MockAgent to intercept real requests and return mocked responses. 6 7## `new MockPool(origin, [options])` 8 9Arguments: 10 11* **origin** `string` - It should only include the **protocol, hostname, and port**. 12* **options** `MockPoolOptions` - It extends the `Pool` options. 13 14Returns: `MockPool` 15 16### Parameter: `MockPoolOptions` 17 18Extends: `PoolOptions` 19 20* **agent** `Agent` - the agent to associate this MockPool with. 21 22### Example - Basic MockPool instantiation 23 24We can use MockAgent to instantiate a MockPool ready to be used to intercept specified requests. It will not do anything until registered as the agent to use and any mock request are registered. 25 26```js 27import { MockAgent } from 'undici' 28 29const mockAgent = new MockAgent() 30 31const mockPool = mockAgent.get('http://localhost:3000') 32``` 33 34## Instance Methods 35 36### `MockPool.intercept(options)` 37 38This method defines the interception rules for matching against requests for a MockPool or MockPool. We can intercept multiple times on a single instance, but each intercept is only used once. For example if you expect to make 2 requests inside a test, you need to call `intercept()` twice. Assuming you use `disableNetConnect()` you will get `MockNotMatchedError` on the second request when you only call `intercept()` once. 39 40When defining interception rules, all the rules must pass for a request to be intercepted. If a request is not intercepted, a real request will be attempted. 41 42| Matcher type | Condition to pass | 43|:------------:| -------------------------- | 44| `string` | Exact match against string | 45| `RegExp` | Regex must pass | 46| `Function` | Function must return true | 47 48Arguments: 49 50* **options** `MockPoolInterceptOptions` - Interception options. 51 52Returns: `MockInterceptor` corresponding to the input options. 53 54### Parameter: `MockPoolInterceptOptions` 55 56* **path** `string | RegExp | (path: string) => boolean` - a matcher for the HTTP request path. When a `RegExp` or callback is used, it will match against the request path including all query parameters in alphabetical order. When a `string` is provided, the query parameters can be conveniently specified through the `MockPoolInterceptOptions.query` setting. 57* **method** `string | RegExp | (method: string) => boolean` - (optional) - a matcher for the HTTP request method. Defaults to `GET`. 58* **body** `string | RegExp | (body: string) => boolean` - (optional) - a matcher for the HTTP request body. 59* **headers** `Record<string, string | RegExp | (body: string) => boolean`> - (optional) - a matcher for the HTTP request headers. To be intercepted, a request must match all defined headers. Extra headers not defined here may (or may not) be included in the request and do not affect the interception in any way. 60* **query** `Record<string, any> | null` - (optional) - a matcher for the HTTP request query string params. Only applies when a `string` was provided for `MockPoolInterceptOptions.path`. 61 62### Return: `MockInterceptor` 63 64We can define the behaviour of an intercepted request with the following options. 65 66* **reply** `(statusCode: number, replyData: string | Buffer | object | MockInterceptor.MockResponseDataHandler, responseOptions?: MockResponseOptions) => MockScope` - define a reply for a matching request. You can define the replyData as a callback to read incoming request data. Default for `responseOptions` is `{}`. 67* **reply** `(callback: MockInterceptor.MockReplyOptionsCallback) => MockScope` - define a reply for a matching request, allowing dynamic mocking of all reply options rather than just the data. 68* **replyWithError** `(error: Error) => MockScope` - define an error for a matching request to throw. 69* **defaultReplyHeaders** `(headers: Record<string, string>) => MockInterceptor` - define default headers to be included in subsequent replies. These are in addition to headers on a specific reply. 70* **defaultReplyTrailers** `(trailers: Record<string, string>) => MockInterceptor` - define default trailers to be included in subsequent replies. These are in addition to trailers on a specific reply. 71* **replyContentLength** `() => MockInterceptor` - define automatically calculated `content-length` headers to be included in subsequent replies. 72 73The reply data of an intercepted request may either be a string, buffer, or JavaScript object. Objects are converted to JSON while strings and buffers are sent as-is. 74 75By default, `reply` and `replyWithError` define the behaviour for the first matching request only. Subsequent requests will not be affected (this can be changed using the returned `MockScope`). 76 77### Parameter: `MockResponseOptions` 78 79* **headers** `Record<string, string>` - headers to be included on the mocked reply. 80* **trailers** `Record<string, string>` - trailers to be included on the mocked reply. 81 82### Return: `MockScope` 83 84A `MockScope` is associated with a single `MockInterceptor`. With this, we can configure the default behaviour of a intercepted reply. 85 86* **delay** `(waitInMs: number) => MockScope` - delay the associated reply by a set amount in ms. 87* **persist** `() => MockScope` - any matching request will always reply with the defined response indefinitely. 88* **times** `(repeatTimes: number) => MockScope` - any matching request will reply with the defined response a fixed amount of times. This is overridden by **persist**. 89 90#### Example - Basic Mocked Request 91 92```js 93import { MockAgent, setGlobalDispatcher, request } from 'undici' 94 95const mockAgent = new MockAgent() 96setGlobalDispatcher(mockAgent) 97 98// MockPool 99const mockPool = mockAgent.get('http://localhost:3000') 100mockPool.intercept({ path: '/foo' }).reply(200, 'foo') 101 102const { 103 statusCode, 104 body 105} = await request('http://localhost:3000/foo') 106 107console.log('response received', statusCode) // response received 200 108 109for await (const data of body) { 110 console.log('data', data.toString('utf8')) // data foo 111} 112``` 113 114#### Example - Mocked request using reply data callbacks 115 116```js 117import { MockAgent, setGlobalDispatcher, request } from 'undici' 118 119const mockAgent = new MockAgent() 120setGlobalDispatcher(mockAgent) 121 122const mockPool = mockAgent.get('http://localhost:3000') 123 124mockPool.intercept({ 125 path: '/echo', 126 method: 'GET', 127 headers: { 128 'User-Agent': 'undici', 129 Host: 'example.com' 130 } 131}).reply(200, ({ headers }) => ({ message: headers.get('message') })) 132 133const { statusCode, body, headers } = await request('http://localhost:3000', { 134 headers: { 135 message: 'hello world!' 136 } 137}) 138 139console.log('response received', statusCode) // response received 200 140console.log('headers', headers) // { 'content-type': 'application/json' } 141 142for await (const data of body) { 143 console.log('data', data.toString('utf8')) // { "message":"hello world!" } 144} 145``` 146 147#### Example - Mocked request using reply options callback 148 149```js 150import { MockAgent, setGlobalDispatcher, request } from 'undici' 151 152const mockAgent = new MockAgent() 153setGlobalDispatcher(mockAgent) 154 155const mockPool = mockAgent.get('http://localhost:3000') 156 157mockPool.intercept({ 158 path: '/echo', 159 method: 'GET', 160 headers: { 161 'User-Agent': 'undici', 162 Host: 'example.com' 163 } 164}).reply(({ headers }) => ({ statusCode: 200, data: { message: headers.get('message') }}))) 165 166const { statusCode, body, headers } = await request('http://localhost:3000', { 167 headers: { 168 message: 'hello world!' 169 } 170}) 171 172console.log('response received', statusCode) // response received 200 173console.log('headers', headers) // { 'content-type': 'application/json' } 174 175for await (const data of body) { 176 console.log('data', data.toString('utf8')) // { "message":"hello world!" } 177} 178``` 179 180#### Example - Basic Mocked requests with multiple intercepts 181 182```js 183import { MockAgent, setGlobalDispatcher, request } from 'undici' 184 185const mockAgent = new MockAgent() 186setGlobalDispatcher(mockAgent) 187 188const mockPool = mockAgent.get('http://localhost:3000') 189 190mockPool.intercept({ 191 path: '/foo', 192 method: 'GET' 193}).reply(200, 'foo') 194 195mockPool.intercept({ 196 path: '/hello', 197 method: 'GET', 198}).reply(200, 'hello') 199 200const result1 = await request('http://localhost:3000/foo') 201 202console.log('response received', result1.statusCode) // response received 200 203 204for await (const data of result1.body) { 205 console.log('data', data.toString('utf8')) // data foo 206} 207 208const result2 = await request('http://localhost:3000/hello') 209 210console.log('response received', result2.statusCode) // response received 200 211 212for await (const data of result2.body) { 213 console.log('data', data.toString('utf8')) // data hello 214} 215``` 216 217#### Example - Mocked request with query body, request headers and response headers and trailers 218 219```js 220import { MockAgent, setGlobalDispatcher, request } from 'undici' 221 222const mockAgent = new MockAgent() 223setGlobalDispatcher(mockAgent) 224 225const mockPool = mockAgent.get('http://localhost:3000') 226 227mockPool.intercept({ 228 path: '/foo?hello=there&see=ya', 229 method: 'POST', 230 body: 'form1=data1&form2=data2', 231 headers: { 232 'User-Agent': 'undici', 233 Host: 'example.com' 234 } 235}).reply(200, { foo: 'bar' }, { 236 headers: { 'content-type': 'application/json' }, 237 trailers: { 'Content-MD5': 'test' } 238}) 239 240const { 241 statusCode, 242 headers, 243 trailers, 244 body 245} = await request('http://localhost:3000/foo?hello=there&see=ya', { 246 method: 'POST', 247 body: 'form1=data1&form2=data2', 248 headers: { 249 foo: 'bar', 250 'User-Agent': 'undici', 251 Host: 'example.com' 252 } 253 }) 254 255console.log('response received', statusCode) // response received 200 256console.log('headers', headers) // { 'content-type': 'application/json' } 257 258for await (const data of body) { 259 console.log('data', data.toString('utf8')) // '{"foo":"bar"}' 260} 261 262console.log('trailers', trailers) // { 'content-md5': 'test' } 263``` 264 265#### Example - Mocked request using different matchers 266 267```js 268import { MockAgent, setGlobalDispatcher, request } from 'undici' 269 270const mockAgent = new MockAgent() 271setGlobalDispatcher(mockAgent) 272 273const mockPool = mockAgent.get('http://localhost:3000') 274 275mockPool.intercept({ 276 path: '/foo', 277 method: /^GET$/, 278 body: (value) => value === 'form=data', 279 headers: { 280 'User-Agent': 'undici', 281 Host: /^example.com$/ 282 } 283}).reply(200, 'foo') 284 285const { 286 statusCode, 287 body 288} = await request('http://localhost:3000/foo', { 289 method: 'GET', 290 body: 'form=data', 291 headers: { 292 foo: 'bar', 293 'User-Agent': 'undici', 294 Host: 'example.com' 295 } 296}) 297 298console.log('response received', statusCode) // response received 200 299 300for await (const data of body) { 301 console.log('data', data.toString('utf8')) // data foo 302} 303``` 304 305#### Example - Mocked request with reply with a defined error 306 307```js 308import { MockAgent, setGlobalDispatcher, request } from 'undici' 309 310const mockAgent = new MockAgent() 311setGlobalDispatcher(mockAgent) 312 313const mockPool = mockAgent.get('http://localhost:3000') 314 315mockPool.intercept({ 316 path: '/foo', 317 method: 'GET' 318}).replyWithError(new Error('kaboom')) 319 320try { 321 await request('http://localhost:3000/foo', { 322 method: 'GET' 323 }) 324} catch (error) { 325 console.error(error) // Error: kaboom 326} 327``` 328 329#### Example - Mocked request with defaultReplyHeaders 330 331```js 332import { MockAgent, setGlobalDispatcher, request } from 'undici' 333 334const mockAgent = new MockAgent() 335setGlobalDispatcher(mockAgent) 336 337const mockPool = mockAgent.get('http://localhost:3000') 338 339mockPool.intercept({ 340 path: '/foo', 341 method: 'GET' 342}).defaultReplyHeaders({ foo: 'bar' }) 343 .reply(200, 'foo') 344 345const { headers } = await request('http://localhost:3000/foo') 346 347console.log('headers', headers) // headers { foo: 'bar' } 348``` 349 350#### Example - Mocked request with defaultReplyTrailers 351 352```js 353import { MockAgent, setGlobalDispatcher, request } from 'undici' 354 355const mockAgent = new MockAgent() 356setGlobalDispatcher(mockAgent) 357 358const mockPool = mockAgent.get('http://localhost:3000') 359 360mockPool.intercept({ 361 path: '/foo', 362 method: 'GET' 363}).defaultReplyTrailers({ foo: 'bar' }) 364 .reply(200, 'foo') 365 366const { trailers } = await request('http://localhost:3000/foo') 367 368console.log('trailers', trailers) // trailers { foo: 'bar' } 369``` 370 371#### Example - Mocked request with automatic content-length calculation 372 373```js 374import { MockAgent, setGlobalDispatcher, request } from 'undici' 375 376const mockAgent = new MockAgent() 377setGlobalDispatcher(mockAgent) 378 379const mockPool = mockAgent.get('http://localhost:3000') 380 381mockPool.intercept({ 382 path: '/foo', 383 method: 'GET' 384}).replyContentLength().reply(200, 'foo') 385 386const { headers } = await request('http://localhost:3000/foo') 387 388console.log('headers', headers) // headers { 'content-length': '3' } 389``` 390 391#### Example - Mocked request with automatic content-length calculation on an object 392 393```js 394import { MockAgent, setGlobalDispatcher, request } from 'undici' 395 396const mockAgent = new MockAgent() 397setGlobalDispatcher(mockAgent) 398 399const mockPool = mockAgent.get('http://localhost:3000') 400 401mockPool.intercept({ 402 path: '/foo', 403 method: 'GET' 404}).replyContentLength().reply(200, { foo: 'bar' }) 405 406const { headers } = await request('http://localhost:3000/foo') 407 408console.log('headers', headers) // headers { 'content-length': '13' } 409``` 410 411#### Example - Mocked request with persist enabled 412 413```js 414import { MockAgent, setGlobalDispatcher, request } from 'undici' 415 416const mockAgent = new MockAgent() 417setGlobalDispatcher(mockAgent) 418 419const mockPool = mockAgent.get('http://localhost:3000') 420 421mockPool.intercept({ 422 path: '/foo', 423 method: 'GET' 424}).reply(200, 'foo').persist() 425 426const result1 = await request('http://localhost:3000/foo') 427// Will match and return mocked data 428 429const result2 = await request('http://localhost:3000/foo') 430// Will match and return mocked data 431 432// Etc 433``` 434 435#### Example - Mocked request with times enabled 436 437```js 438import { MockAgent, setGlobalDispatcher, request } from 'undici' 439 440const mockAgent = new MockAgent() 441setGlobalDispatcher(mockAgent) 442 443const mockPool = mockAgent.get('http://localhost:3000') 444 445mockPool.intercept({ 446 path: '/foo', 447 method: 'GET' 448}).reply(200, 'foo').times(2) 449 450const result1 = await request('http://localhost:3000/foo') 451// Will match and return mocked data 452 453const result2 = await request('http://localhost:3000/foo') 454// Will match and return mocked data 455 456const result3 = await request('http://localhost:3000/foo') 457// Will not match and make attempt a real request 458``` 459 460#### Example - Mocked request with path callback 461 462```js 463import { MockAgent, setGlobalDispatcher, request } from 'undici' 464import querystring from 'querystring' 465 466const mockAgent = new MockAgent() 467setGlobalDispatcher(mockAgent) 468 469const mockPool = mockAgent.get('http://localhost:3000') 470 471const matchPath = requestPath => { 472 const [pathname, search] = requestPath.split('?') 473 const requestQuery = querystring.parse(search) 474 475 if (!pathname.startsWith('/foo')) { 476 return false 477 } 478 479 if (!Object.keys(requestQuery).includes('foo') || requestQuery.foo !== 'bar') { 480 return false 481 } 482 483 return true 484} 485 486mockPool.intercept({ 487 path: matchPath, 488 method: 'GET' 489}).reply(200, 'foo') 490 491const result = await request('http://localhost:3000/foo?foo=bar') 492// Will match and return mocked data 493``` 494 495### `MockPool.close()` 496 497Closes the mock pool and de-registers from associated MockAgent. 498 499Returns: `Promise<void>` 500 501#### Example - clean up after tests are complete 502 503```js 504import { MockAgent } from 'undici' 505 506const mockAgent = new MockAgent() 507const mockPool = mockAgent.get('http://localhost:3000') 508 509await mockPool.close() 510``` 511 512### `MockPool.dispatch(options, handlers)` 513 514Implements [`Dispatcher.dispatch(options, handlers)`](Dispatcher.md#dispatcherdispatchoptions-handler). 515 516### `MockPool.request(options[, callback])` 517 518See [`Dispatcher.request(options [, callback])`](Dispatcher.md#dispatcherrequestoptions-callback). 519 520#### Example - MockPool request 521 522```js 523import { MockAgent } from 'undici' 524 525const mockAgent = new MockAgent() 526 527const mockPool = mockAgent.get('http://localhost:3000') 528mockPool.intercept({ 529 path: '/foo', 530 method: 'GET', 531}).reply(200, 'foo') 532 533const { 534 statusCode, 535 body 536} = await mockPool.request({ 537 origin: 'http://localhost:3000', 538 path: '/foo', 539 method: 'GET' 540}) 541 542console.log('response received', statusCode) // response received 200 543 544for await (const data of body) { 545 console.log('data', data.toString('utf8')) // data foo 546} 547``` 548