Skip to main content
Deno 2 is finally here 🎉️
Learn more

deno_mock_fetch

codecov

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

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"

Autogenerate content-length header

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:

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:

License

This module is 100% free and open-source, under the MIT license.

Inspirations

This modules has been inspired by the following: