deno_mock_fetch
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
Set up a basic fetch
interceptor.
import { MockFetch } from "https://deno.land/x/deno_mock_fetch@0.3.0/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:
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
:
// Rejects with MockNotMatchedError
await fetch("https://example.com/hello");
This behaviour can be changed as demonstrated in the examples.
Examples
I want to:
- Intercept a request containing a Query String
- Intercept a request indefinitely
- Intercept a request a set number of times
- Intercept a request with a delay
- Default to calling the original URL if a mock is not matched
- Default to calling specified URLs if a mock is not matched
- Deactivate calling original URLs
- Activate fetch interceptions
- Deactivate fetch interceptions
- Check if fetch interceptions are active
- Close and clean up the Mock Fetch instance
- Get the number of times requests have been intercepted
- View registered mock metadata
- Intercept a request based on method RegExp
- Intercept a request based on method Function
- Intercept a request based on URL RegExp
- Intercept a request based on URL Function
- Intercept a request based on body
- Intercept a request based on headers object
- Intercept a request based on headers instance
- Intercept a request based on headers array
- Intercept a request based on headers function
- Throw a custom error upon fetch call
- Set default headers
- Autogenerate
content-length
header - Intercept requests alongside superdeno
Intercept a request containing a Query String
Set up the interceptor.
import { MockFetch } from "https://deno.land/x/deno_mock_fetch@0.3.0/mod.ts";
const mockFetch = new MockFetch();
mockFetch
.intercept("https://example.com/hello?foo=bar", { method: "GET" })
.response("hello", { status: 200 });
Call the matching URL:
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.
import { MockFetch } from "https://deno.land/x/deno_mock_fetch@0.3.0/mod.ts";
const mockFetch = new MockFetch();
mockFetch
.intercept("https://example.com/hello", { method: "GET" })
.response("hello", { status: 200 })
.persist();
Call the matching URL:
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:
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.
import { MockFetch } from "https://deno.land/x/deno_mock_fetch@0.3.0/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:
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:
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:
// 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.
import { MockFetch } from "https://deno.land/x/deno_mock_fetch@0.3.0/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:
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.
import { MockFetch } from "https://deno.land/x/deno_mock_fetch@0.3.0/mod.ts";
const mockFetch = new MockFetch();
mockFetch.activateNetConnect();
mockFetch
.intercept("https://example.com/hello", { method: "GET" })
.response("hello", { status: 200 });
Call the matching URL:
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:
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.
import { MockFetch } from "https://deno.land/x/deno_mock_fetch@0.3.0/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:
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:
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.
import { MockFetch } from "https://deno.land/x/deno_mock_fetch@0.3.0/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.
import { MockFetch } from "https://deno.land/x/deno_mock_fetch@0.3.0/mod.ts";
const mockFetch = new MockFetch();
mockFetch.activate();
Deactivate fetch interceptions
Deactivate fetch interceptions by calling the deactivate
method on the
MockFetch
instance.
import { MockFetch } from "https://deno.land/x/deno_mock_fetch@0.3.0/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.
import { MockFetch } from "https://deno.land/x/deno_mock_fetch@0.3.0/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.
import { MockFetch } from "https://deno.land/x/deno_mock_fetch@0.3.0/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.
import { MockFetch } from "https://deno.land/x/deno_mock_fetch@0.3.0/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.
import { MockFetch } from "https://deno.land/x/deno_mock_fetch@0.3.0/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.
import { MockFetch } from "https://deno.land/x/deno_mock_fetch@0.3.0/mod.ts";
const mockFetch = new MockFetch();
mockFetch
.intercept("https://example.com/hello", { method: /GET/ })
.response("hello", { status: 200 });
Call the matching URL:
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.
import { MockFetch } from "https://deno.land/x/deno_mock_fetch@0.3.0/mod.ts";
const mockFetch = new MockFetch();
mockFetch
.intercept("https://example.com/hello", {
method: (input) => input === "GET",
})
.response("hello", { status: 200 });
Call the matching URL:
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.
import { MockFetch } from "https://deno.land/x/deno_mock_fetch@0.3.0/mod.ts";
const mockFetch = new MockFetch();
mockFetch
.intercept(new RegExp("https\\:\\/\\/example\\.com\\/hello\\?foo\\=bar"))
.response("hello", { status: 200 });
Call the matching URL:
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.
import { MockFetch } from "https://deno.land/x/deno_mock_fetch@0.3.0/mod.ts";
const mockFetch = new MockFetch();
mockFetch
.intercept((input) => input === "https://example.com/hello?foo=bar")
.response("hello", { status: 200 });
Call the matching URL:
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.
import { MockFetch } from "https://deno.land/x/deno_mock_fetch@0.3.0/mod.ts";
const mockFetch = new MockFetch();
mockFetch
.intercept("https://example.com/hello", {
method: "POST",
body: "hello",
})
.response("there", { status: 200 });
Call the matching URL:
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.
import { MockFetch } from "https://deno.land/x/deno_mock_fetch@0.3.0/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:
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.
import { MockFetch } from "https://deno.land/x/deno_mock_fetch@0.3.0/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:
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.
import { MockFetch } from "https://deno.land/x/deno_mock_fetch@0.3.0/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:
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.
import { MockFetch } from "https://deno.land/x/deno_mock_fetch@0.3.0/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:
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 as a guide.
import { MockFetch } from "https://deno.land/x/deno_mock_fetch@0.3.0/mod.ts";
const mockFetch = new MockFetch();
mockFetch
.intercept((input) => input === "https://example.com/hello")
.throwError(new TypeError("Network error"));
Call the matching URL:
await fetch("https://example.com/hello"); // Throws the defined error: new TypeError("Network error")
Set default headers
Set up the interceptor.
import { MockFetch } from "https://deno.land/x/deno_mock_fetch@0.3.0/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:
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"
content-length
header
Autogenerate import { MockFetch } from "https://deno.land/x/deno_mock_fetch@0.3.0/mod.ts";
const mockFetch = new MockFetch();
const mockInterceptor = mockFetch
.intercept("https://example.com/hello")
.responseContentLength()
.response("hello", { status: 200 });
Call the matching URL:
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.
import { MockFetch } from "https://deno.land/x/deno_mock_fetch@0.3.0/mod.ts";
import { superdeno } from "https://deno.land/x/superdeno/mod.ts";
import { opine } from "https://deno.land/x/opine@1.9.1/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.
Limitations
The following limitations are known:
Cannot intercept with a ReadableStream as the request body
The following with throw an InvalidArgumentError
:
import { MockFetch } from "https://deno.land/x/deno_mock_fetch@0.3.0/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
- Limited support and documentation
License
This module is 100% free and open-source, under the MIT license.
Inspirations
This modules has been inspired by the following: