@juit/lib-fetch-mock
Version:
Easy Mocking of Node.js' own `fetch`
143 lines (103 loc) • 4.83 kB
Markdown
Mocking `fetch` in NodeJS
=========================
This package allows to quicly mock the global `fetch` distributed with Node 18
(and greater).
* [Request Handlers](#request-handlers)
* [Wrapping Mocks](#wrapping-mocks)
* [Request Interception](#request-interception)
* [License](LICENSE.md)
* [Copyright](NOTICE.md)
### Request Handlers
To mock requests to be handled with either the `handle(...)` or `on(...)`
methods exposed by the `FetchMock` class. An example:
```ts
import { FetchMock } from '@juit/lib-fetch-mock'
it('should always return 599', async () => {
const mock = new FetchMock().install()
mock.handle((request: Request) => {
return new Response(null, { status: 599 })
})
const response = await fetch('https://www.google.com/')
expect(response.status).toEqual(599)
})
```
We can also mock individual HTTP calls, by using `on(...)` with a _method_ and
path (either matched by `string` or `RegExp`):
```ts
import { FetchMock } from '@juit/lib-fetch-mock'
it('should always return 404', async () => {
const mock = new FetchMock().install()
mock.on('GET', '/foo', (request: Request) => 200)
.on('POST', /bar/, (request: Request) => 302)
expect((await fetch('https://www.google.com/foo')).status).toEqual(200)
expect((await fetch('https://www.apple.com/bar')).status).toEqual(302)
expect((await fetch('https://www.microsoft.com')).status).toEqual(404) // default!
})
```
The second parameter to the callback for `handle(...)` or `on(...)` is always
a _real_ `fetch` method, allowing to interact with the network.
The functions `sendStatus(...)`, `sendJson(...)`, `sendText(...)` and
`sendData(...)` can be used to generate simple responses for mocking.
### Wrapping Mocks
Mock instances can be wrapped to support in testing. For example:
```ts
const handler1 = new FetchMock()
handler1.on('GET', '/foo', (_) => new Response('FOO'))
handler1.on('GET', '/baz', (_) => new Response('FOO'))
const handler2 = new FetchMock()
handler2.on('GET', '/foo', (_) => new Response('Another FOO'))
handler2.on('GET', '/bar', (_) => new Response('BAR'))
handler1.install()
handler2.install() // this will be the _top most_ as it's installed later
```
The last installed `FetchMock` instance will be the _top-most_ capturing all
requests and forwarding requests to sub-mocks only when not matched.
In the example above:
* requests for `/foo` will be intercepted by `handler2` and _not_ forwarded to `handler1`
* requests for `/bar` will be handled locally
* requests for `/baz` will simply be forwarded to `handler1`
### Request Interception
As in some case it's easier to _await_ in tests for a specific request (rather
than constructing a whole interceptor handling multiple cases) the function
`interceptor()` gives access to a pseudo-iterator for `Request`s.
An example:
```ts
import { FetchMock } from '@juit/lib-fetch-mock'
it('should await on some requests', async () => {
const mock = new FetchMock().install()
const next = mock.intercept() // our interceptor!
// Don't _await_ on this, it'll be responded to below
const promise = fetch('https://www.apple.com/')
// Here "request" is the `Request` associated with the request above
const request = await next()
// Obviously, we can inspect the whole request, url, headers, body, ...
expect(request.url).toEqual('https://www.apple.com/')
// Now we can _respond_ to our request, with a `Response`, json, ...
request.sendJson({ apple: 'and banana' })
// Here the promise to the request is resolved, sooo..
const response = await promise
expect(response.status).toEqual(200)
expect(await response.json()).toEqual({ apple: 'and banana' })
})
```
The `DeferredResponse` returned by the interceptor (`next()` in the example
above) exposes few methods to easily create responses:
* `fail: (error?: Error) => void` \
Fail the `Request` with an optional `Error`.
* `fetch: (...args: FetchArguments) => void` \
Respond to this intercepted request with a _real_ `fetch` request.
* `send: (response?: Response | PromiseLike<Response>) => void` \
Respond to the `Request` with an optional `Response` (defaults to a `200 Ok`
response with no/empty body).
* `sendStatus: (status: number, statusText?: string) => void` \
Respond to the `Request` only with the specified status and an empty body,
the status text can optionally be specified, as well
* `sendJson: (json: any, status?: number) => void`
Respond to the `Request` only with the specified JSON body and an optional
status code.
* `sendText: (text: string, status?: number) => void`
Respond to the `Request` only with the specified text body and an optional
status code.
* `sendData: (data: Uint8Array, status?: number) => void`
Respond to the `Request` only with the specified binary body and an optional
status code.