Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/jonnydgreen/deno-mock-fetch
Deno mock fetch implementation
https://github.com/jonnydgreen/deno-mock-fetch
Last synced: 8 days ago
JSON representation
Deno mock fetch implementation
- Host: GitHub
- URL: https://github.com/jonnydgreen/deno-mock-fetch
- Owner: jonnydgreen
- License: mit
- Created: 2022-11-09T21:42:30.000Z (about 2 years ago)
- Default Branch: main
- Last Pushed: 2022-11-26T13:38:52.000Z (almost 2 years ago)
- Last Synced: 2023-08-08T20:47:38.720Z (over 1 year ago)
- Language: TypeScript
- Size: 143 KB
- Stars: 5
- Watchers: 1
- Forks: 0
- Open Issues: 1
-
Metadata Files:
- Readme: README.md
- Contributing: CONTRIBUTING.md
- License: LICENSE
- Code of conduct: CODE_OF_CONDUCT.md
- Codeowners: .github/CODEOWNERS
Awesome Lists containing this project
README
# deno_mock_fetch
[![codecov](https://codecov.io/gh/jonnydgreen/deno-mock-fetch/branch/main/graph/badge.svg)](https://codecov.io/gh/jonnydgreen/deno-mock-fetch)
[Deno mock fetch](https://deno.land/x/deno_mock_fetch) implementation to be used
in testing. This module allows one to intercept calls to the global `fetch` API
and control the behaviour accordingly.## Features
- Intercept calls to the global `fetch` API
- Intercept multiple types of requests at once, based on:
- Request Origin
- Request Path
- Request Query string
- Request Body
- Request Headers
- Intercept request indefinitely
- Intercept request a finite number of times
- Simulate a request time delay
- Support for falling back to calling a real API for defined hostnames
- All global `fetch` API inputs are supported
- Advanced methods for matching requests:
- `string`
- `RegExp`
- `Function`
- Throw custom error support
- Set default headers
- Auto-generated headers:
- `content-length`## Table of Contents
- [Quick Start](#quick-start)
- [API Documentation](#api-documentation)
- [Examples](#examples)
- [Contributing](#contributing)
- [Limitations](#limitations)
- [License](#license)
- [Inspirations](#inspirations)## Quick Start
Set up a basic `fetch` interceptor.
```typescript
import { MockFetch } from "https://deno.land/x/[email protected]/mod.ts";const mockFetch = new MockFetch();
mockFetch
// Intercept `GET https://example.com/hello`
.intercept("https://example.com/hello", { method: "GET" })
// Respond with status `200` and text `hello`
.response("hello", { status: 200 });
```Call the matching URL:
```typescript
const response = await fetch("https://example.com/hello");const text = await response.text();
console.log(response.status); // 200
console.log(text); // "hello"
```By default, subsequent calls to the same URL will reject with a
`MockNotMatchedError`:```typescript
// Rejects with MockNotMatchedError
await fetch("https://example.com/hello");
```This behaviour can be changed as demonstrated in the [examples](#examples).
## Examples
I want to:
- [Intercept a request containing a Query String](#intercept-a-request-containing-a-query-string)
- [Intercept a request indefinitely](#intercept-a-request-indefinitely)
- [Intercept a request a set number of times](#intercept-a-request-a-set-number-of-times)
- [Intercept a request with a delay](#intercept-a-request-with-a-delay)
- [Default to calling the original URL if a mock is not matched](#default-to-calling-the-original-url-if-a-mock-is-not-matched)
- [Default to calling specified URLs if a mock is not matched](#default-to-calling-specified-urls-if-a-mock-is-not-matched)
- [Deactivate calling original URLs](#deactivate-calling-original-urls)
- [Activate fetch interceptions](#activate-fetch-interceptions)
- [Deactivate fetch interceptions](#deactivate-fetch-interceptions)
- [Check if fetch interceptions are active](#check-if-fetch-interceptions-are-active)
- [Close and clean up the Mock Fetch instance](#close-and-clean-up-the-mock-fetch-instance)
- [Get the number of times requests have been intercepted](#get-the-number-of-times-requests-have-been-intercepted)
- [View registered mock metadata](#view-registered-mock-metadata)
- [Intercept a request based on method RegExp](#intercept-a-request-based-on-method-regexp)
- [Intercept a request based on method Function](#intercept-a-request-based-on-method-function)
- [Intercept a request based on URL RegExp](#intercept-a-request-based-on-url-regexp)
- [Intercept a request based on URL Function](#intercept-a-request-based-on-url-function)
- [Intercept a request based on body](#intercept-a-request-based-on-body)
- [Intercept a request based on headers object](#intercept-a-request-based-on-headers-object)
- [Intercept a request based on headers instance](#intercept-a-request-based-on-headers-instance)
- [Intercept a request based on headers array](#intercept-a-request-based-on-headers-array)
- [Intercept a request based on headers function](#intercept-a-request-based-on-headers-function)
- [Throw a custom error upon fetch call](#throw-a-custom-error-upon-fetch-call)
- [Set default headers](#set-default-headers)
- [Autogenerate `content-length` header](#autogenerate-content-length-header)
- [Intercept requests alongside superdeno](#intercept-requests-alongside-superdeno)### Intercept a request containing a Query String
Set up the interceptor.
```typescript
import { MockFetch } from "https://deno.land/x/[email protected]/mod.ts";const mockFetch = new MockFetch();
mockFetch
.intercept("https://example.com/hello?foo=bar", { method: "GET" })
.response("hello", { status: 200 });
```Call the matching URL:
```typescript
const response = await fetch("https://example.com/hello?foo=bar");const text = await response.text();
console.log(response.status); // 200
console.log(text); // "hello"
```### Intercept a request indefinitely
Set up the interceptor, using the `persist` method on the `MockScope` instance.
```typescript
import { MockFetch } from "https://deno.land/x/[email protected]/mod.ts";const mockFetch = new MockFetch();
mockFetch
.intercept("https://example.com/hello", { method: "GET" })
.response("hello", { status: 200 })
.persist();
```Call the matching URL:
```typescript
const response = await fetch("https://example.com/hello");const text = await response.text();
console.log(response.status); // 200
console.log(text); // "hello"
```Call the matching URL again:
```typescript
const response = await fetch("https://example.com/hello");const text = await response.text();
console.log(response.status); // 200
console.log(text); // "hello"
```### Intercept a request a set number of times
Set up the interceptor, using the `times` method on the `MockScope` instance.
```typescript
import { MockFetch } from "https://deno.land/x/[email protected]/mod.ts";const mockFetch = new MockFetch();
mockFetch
.intercept("https://example.com/hello", { method: "GET" })
.response("hello", { status: 200 })
// Will intercept matching requests twice
.times(2);
```Call the matching URL:
```typescript
const response = await fetch("https://example.com/hello");const text = await response.text();
console.log(response.status); // 200
console.log(text); // "hello"
```Call the matching URL again:
```typescript
const response = await fetch("https://example.com/hello");const text = await response.text();
console.log(response.status); // 200
console.log(text); // "hello"
```Call the matching URL a final time:
```typescript
// Rejects with MockNotMatchedError
await fetch("https://example.com/hello");
```### Intercept a request with a delay
Set up the interceptor, using the `delay` method on the `MockScope` instance.
```typescript
import { MockFetch } from "https://deno.land/x/[email protected]/mod.ts";const mockFetch = new MockFetch();
mockFetch
.intercept("https://example.com/hello", { method: "GET" })
.response("hello", { status: 200 })
// Delay 1000ms before returning the response
.delay(1000);
```Call the matching URL:
```typescript
const response = await fetch("https://example.com/hello");// 1000ms later...
const text = await response.text();
console.log(response.status); // 200
console.log(text); // "hello"
```### Default to calling the original URL if a mock is not matched
Sometimes, one might want to default to calling the original URL if a mock is
not matched. This can be done with the `activateNetConnect` method on the
`MockFetch` instance.```typescript
import { MockFetch } from "https://deno.land/x/[email protected]/mod.ts";const mockFetch = new MockFetch();
mockFetch.activateNetConnect();
mockFetch
.intercept("https://example.com/hello", { method: "GET" })
.response("hello", { status: 200 });
```Call the matching URL:
```typescript
const response = await fetch("https://example.com/hello");const text = await response.text();
console.log(response.status); // 200
console.log(text); // "hello"
```Call the same URL:
```typescript
const response = await fetch("https://example.com/hello");const text = await response.text();
console.log(response.status); // 200
console.log(text); // Some html from the actual endpoint
```### Default to calling specified URLs if a mock is not matched
In addition, one might want to default to calling the original URL for certain
hostnames if a mock is not matched. This can be done by passing matchers to the
`activateNetConnect` method on the `MockFetch` instance.```typescript
import { MockFetch } from "https://deno.land/x/[email protected]/mod.ts";const mockFetch = new MockFetch();
// Allow calls to `example.com` upon an unmatched request.
// This can be called multiple to times to register multiple hostnames
mockFetch.activateNetConnect("example.com");
```Call a non-matching URL:
```typescript
const response = await fetch("https://example.com/hello");const text = await response.text();
console.log(response.status); // 200
console.log(text); // Some html from the actual endpoint
```Call another non-matching URL:
```typescript
const response = await fetch("https://another-example.com/hello");// Rejects with MockNotMatchedError
await fetch("https://example.com/hello");
```### Deactivate calling original URLs
Deactivate calling original URLs by calling the `deactivateNetConnect` on the
`MockFetch` instance.```typescript
import { MockFetch } from "https://deno.land/x/[email protected]/mod.ts";const mockFetch = new MockFetch();
mockFetch.activateNetConnect();
// Do work...
mockFetch.deactivateNetConnect();
```### Activate fetch interceptions
Activate fetch interceptions by calling the `activate` method on the `MockFetch`
instance.```typescript
import { MockFetch } from "https://deno.land/x/[email protected]/mod.ts";const mockFetch = new MockFetch();
mockFetch.activate();
```### Deactivate fetch interceptions
Deactivate fetch interceptions by calling the `deactivate` method on the
`MockFetch` instance.```typescript
import { MockFetch } from "https://deno.land/x/[email protected]/mod.ts";const mockFetch = new MockFetch();
mockFetch.deactivate();
```### Check if fetch interceptions are active
Check if fetch interceptions are active by checking the value of the
`isMockActive` field on the `MockFetch` instance.```typescript
import { MockFetch } from "https://deno.land/x/[email protected]/mod.ts";const mockFetch = new MockFetch();
console.log(mockFetch.isMockActive); // true
```### Close and clean up the Mock Fetch instance
Close and clean up the Mock Fetch instance by calling the `close` method on the
`MockFetch` instance.```typescript
import { MockFetch } from "https://deno.land/x/[email protected]/mod.ts";const mockFetch = new MockFetch();
mockFetch.close();
```### Get the number of times requests have been intercepted
Get the number of times requests have been intercepted by checking the value of
the `calls` field on the `MockFetch` instance.```typescript
import { MockFetch } from "https://deno.land/x/[email protected]/mod.ts";const mockFetch = new MockFetch();
console.log(mockFetch.calls); // 0
mockFetch
.intercept("https://example.com/hello?foo=bar", { method: "GET" })
.response("hello", { status: 200 });const response = await fetch("https://example.com/hello");
const text = await response.text();
console.log(response.status); // 200
console.log(text); // "hello"console.log(mockFetch.calls); // 1
```### View registered mock metadata
View registered mock scope metadata by checking the value of the `metadata`
field on the `MockScope` instance.```typescript
import { MockFetch } from "https://deno.land/x/[email protected]/mod.ts";const mockFetch = new MockFetch();
console.log(mockFetch.calls); // 0
const mockScope = mockFetch
.intercept("https://example.com/hello?foo=bar", { method: "GET" })
.response("hello", { status: 200 });console.log(mockScope.metadata); // MockRequest
```### Intercept a request based on method RegExp
Set up the interceptor.
```typescript
import { MockFetch } from "https://deno.land/x/[email protected]/mod.ts";const mockFetch = new MockFetch();
mockFetch
.intercept("https://example.com/hello", { method: /GET/ })
.response("hello", { status: 200 });
```Call the matching URL:
```typescript
const response = await fetch("https://example.com/hello");const text = await response.text();
console.log(response.status); // 200
console.log(text); // "hello"
```### Intercept a request based on method Function
Set up the interceptor.
```typescript
import { MockFetch } from "https://deno.land/x/[email protected]/mod.ts";const mockFetch = new MockFetch();
mockFetch
.intercept("https://example.com/hello", {
method: (input) => input === "GET",
})
.response("hello", { status: 200 });
```Call the matching URL:
```typescript
const response = await fetch("https://example.com/hello");const text = await response.text();
console.log(response.status); // 200
console.log(text); // "hello"
```### Intercept a request based on URL RegExp
Set up the interceptor.
```typescript
import { MockFetch } from "https://deno.land/x/[email protected]/mod.ts";const mockFetch = new MockFetch();
mockFetch
.intercept(new RegExp("https\\:\\/\\/example\\.com\\/hello\\?foo\\=bar"))
.response("hello", { status: 200 });
```Call the matching URL:
```typescript
const response = await fetch("https://example.com/hello?foo=bar");const text = await response.text();
console.log(response.status); // 200
console.log(text); // "hello"
```### Intercept a request based on URL Function
Set up the interceptor.
```typescript
import { MockFetch } from "https://deno.land/x/[email protected]/mod.ts";const mockFetch = new MockFetch();
mockFetch
.intercept((input) => input === "https://example.com/hello?foo=bar")
.response("hello", { status: 200 });
```Call the matching URL:
```typescript
const response = await fetch("https://example.com/hello?foo=bar");const text = await response.text();
console.log(response.status); // 200
console.log(text); // "hello"
```### Intercept a request based on body
Set up the interceptor.
```typescript
import { MockFetch } from "https://deno.land/x/[email protected]/mod.ts";const mockFetch = new MockFetch();
mockFetch
.intercept("https://example.com/hello", {
method: "POST",
body: "hello",
})
.response("there", { status: 200 });
```Call the matching URL:
```typescript
const response = await fetch("https://example.com/hello", {
method: "POST",
body: "hello",
});const text = await response.text();
console.log(response.status); // 200
console.log(text); // "there"
```Note, the following body types are also supported:
- `string`
- `RegExp`
- `(input: string) => boolean`
- `Blob`
- `ArrayBufferLike`
- `FormData`
- `URLSearchParams`### Intercept a request based on headers object
Set up the interceptor.
```typescript
import { MockFetch } from "https://deno.land/x/[email protected]/mod.ts";const mockFetch = new MockFetch();
mockFetch
.intercept("https://example.com/hello", {
headers: {
hello: "there",
foo: /bar/,
hey: (input: string) => input === "ho",
},
})
.response("hello", { status: 200 });
```Call the matching URL:
```typescript
const response = await fetch("https://example.com/hello", {
headers: new Headers({
hello: "there",
foo: "bar",
hey: "ho",
}),
});const text = await response.text();
console.log(response.status); // 200
console.log(text); // "hello"
```### Intercept a request based on headers instance
Set up the interceptor.
```typescript
import { MockFetch } from "https://deno.land/x/[email protected]/mod.ts";const mockFetch = new MockFetch();
mockFetch
.intercept("https://example.com/hello", {
headers: new Headers({
hello: "there",
another: "one",
}),
})
.response("hello", { status: 200 });
```Call the matching URL:
```typescript
const response = await fetch("https://example.com/hello", {
headers: new Headers({
hello: "there",
another: "one",
}),
});const text = await response.text();
console.log(response.status); // 200
console.log(text); // "hello"
```### Intercept a request based on headers array
Set up the interceptor.
```typescript
import { MockFetch } from "https://deno.land/x/[email protected]/mod.ts";const mockFetch = new MockFetch();
mockFetch
.intercept("https://example.com/hello", {
headers: [
["hello", "there"],
["foo", /bar/],
["hey", (input: string) => input === "ho"],
],
})
.response("hello", { status: 200 });
```Call the matching URL:
```typescript
const response = await fetch("https://example.com/hello", {
headers: new Headers({
hello: "there",
foo: "bar",
hey: "ho",
}),
});const text = await response.text();
console.log(response.status); // 200
console.log(text); // "hello"
```### Intercept a request based on headers function
Set up the interceptor.
```typescript
import { MockFetch } from "https://deno.land/x/[email protected]/mod.ts";const mockFetch = new MockFetch();
mockFetch
.intercept("https://example.com/hello", {
headers: (headers) => headers.get("hello") === "there",
})
.response("hello", { status: 200 });
```Call the matching URL:
```typescript
const response = await fetch("https://example.com/hello", {
headers: new Headers({
hello: "there",
}),
});const text = await response.text();
console.log(response.status); // 200
console.log(text); // "hello"
```### Throw a custom error upon fetch call
Set up the interceptor and defined an error using the following
[documentation](https://developer.mozilla.org/en-US/docs/Web/API/fetch#exceptions)
as a guide.```typescript
import { MockFetch } from "https://deno.land/x/[email protected]/mod.ts";const mockFetch = new MockFetch();
mockFetch
.intercept((input) => input === "https://example.com/hello")
.throwError(new TypeError("Network error"));
```Call the matching URL:
```typescript
await fetch("https://example.com/hello"); // Throws the defined error: new TypeError("Network error")
```### Set default headers
Set up the interceptor.
```typescript
import { MockFetch } from "https://deno.land/x/[email protected]/mod.ts";const mockFetch = new MockFetch();
const mockInterceptor = mockFetch
.intercept("https://example.com/hello")
.defaultResponseHeaders({ foo: "bar" })
.response("hello", { status: 200 });
```Call the matching URL:
```typescript
const response = await fetch("https://example.com/hello");
const text = await response.text();console.log(response.status); // 200
console.log(response.headers); // { "content-type", "text/plain;charset=UTF-8", foo: "bar" }
console.log(text); // "hello"
```### Autogenerate `content-length` header
```typescript
import { MockFetch } from "https://deno.land/x/[email protected]/mod.ts";const mockFetch = new MockFetch();
const mockInterceptor = mockFetch
.intercept("https://example.com/hello")
.responseContentLength()
.response("hello", { status: 200 });
```Call the matching URL:
```typescript
const response = await fetch("https://example.com/hello");
const text = await response.text();console.log(response.status); // 200
console.log(response.headers); // { "content-type", "text/plain;charset=UTF-8", "content-length": "5" }
console.log(text); // "hello"
```### Intercept requests alongside superdeno
To work alongside superdeno, one must setup calls to `127.0.0.1` before
continuing.```typescript
import { MockFetch } from "https://deno.land/x/[email protected]/mod.ts";
import { superdeno } from "https://deno.land/x/superdeno/mod.ts";
import { opine } from "https://deno.land/x/[email protected]/mod.ts";const app = opine();
app.get("/user", (req, res) => {
res.setStatus(200).json({ name: "Deno" });
});// Setup mock before calling superdeno
const mockFetch = new MockFetch();
mockFetch.activateNetConnect("127.0.0.1");superdeno(app)
.get("/user")
.expect("Content-Type", /json/)
.expect("Content-Length", "15")
.expect(200)
.end((err, res) => {
if (err) throw err;
});
```## API Documentation
To browse API documentation:
- Go to https://deno.land/x/deno_mock_fetch.
- Click "View Documentation".## Contributing
Contributions, issues and feature requests are very welcome. If you are using
this package and fixed a bug for yourself, please consider submitting a PR!Further details can be found in the [Contributing guide](./CONTRIBUTING.md).
## Limitations
The following limitations are known:
### Cannot intercept with a ReadableStream as the request body
The following with throw an `InvalidArgumentError`:
```typescript
import { MockFetch } from "https://deno.land/x/[email protected]/mod.ts";const mockFetch = new MockFetch();
mockFetch.intercept("https://example.com/hello", {
method: "POST",
body: new ReadableStream(),
});
// Throws an `InvalidArgumentError`
```### No support for trailers
Trailers are currently not supported for the following reasons:
- Not returned in
[Deno fetch responses](https://github.com/denoland/deno/issues/16200)
- Limited
[support and documentation](https://developer.mozilla.org/en-US/docs/Web/API/Response)## License
This module is 100% free and open-source, under the [MIT license](./LICENSE).
## Inspirations
This modules has been inspired by the following:
- [`undici`](https://www.npmjs.com/package/undici)
- [`nock`](https://www.npmjs.com/package/nock)