Skip to content

Simple express mock server with a flexible API inspired by fetch-mock

License

Notifications You must be signed in to change notification settings

joshuajaco/mocaron

Repository files navigation

mocaron

npm downloads Coverage Status code style: prettier

Simple express mock server with a flexible API inspired by fetch-mock



Installation

# npm
npm install -D mocaron

# yarn
yarn add -D mocaron

# pnpm
pnpm add -D mocaron

Quick Start

import { MockServer } from "mocaron";

const mockServer = new MockServer({ port: 3000 });

await mockServer.start();

mockServer.get("/test", { status: 200, body: { message: "Hello World" } });

const response = await fetch("http://localhost:3000/test");

console.log(response.status); // 200
console.log(await response.json()); // { message: "Hello World" }

await mockServer.stop();

Usage

Starting and stopping the mock server

See constructor() start() stop()

import { MockServer } from "mocaron";

const mockServer = new MockServer({ port: 3000 });

await mockServer.start();

// ...

await mockServer.stop();

Registering a mock

Register a mock using mock().

mockServer.mock(
  { path: "/test", method: "GET" },
  { status: 200, body: { message: "Hello World" } },
);

const response = await fetch("http://localhost:3000/test");

console.log(response.status); // 200
console.log(await response.json()); // { message: "Hello World" }

Method specific mocks

You can also register mocks that only match a specific HTTP method.

See get() post() put() patch() delete()

mockServer
  .get("/test", { status: 200, body: { message: "Hello World" } })
  .post("/test", { status: 201, body: { message: "Created" } })
  .put("/test", { status: 200, body: { message: "Replaced" } })
  .patch("/test", { status: 200, body: { message: "Updated" } })
  .delete("/test", { status: 204 });

Unmatched requests

If a request does not match any of the registered mocks the server will respond with a 404 status code.

const response = await fetch("http://localhost:3000/test");

console.log(response.status); // 404

Ambiguous mocks

If two or more mocks match the same request the server will respond with a 404 status code.

mockServer.mock({ path: "/foo" }, "foo").mock({ path: "/foo" }, "bar");

const response = await fetch("http://localhost:3000/foo");

console.log(response.status); // 404

You can override this behavior by passing the overwrite option to the last matching mock.

mockServer
  .mock({ path: "/foo" }, "foo")
  .mock({ path: "/foo" }, "bar", { overwrite: true });

const response = await fetch("http://localhost:3000/foo");

console.log(response.status); // 200
console.log(await response.text()); // bar

Resetting the mock server

Calling reset() will reset the mock server to its initial state.

mockServer.get("/test", { status: 200 });

let response = await fetch("http://localhost:3000/test");
console.log(response.status); // 200

mockServer.reset();

response = await fetch("http://localhost:3000/test");
console.log(response.status); // 404

Testing

Set up the mock server for each test using start(), stop() and reset().

import { MockServer } from "mocaron";
import { beforeAll, afterAll, beforeEach, test, assert } from "my-test-library";

const mockServer = new MockServer({ port: 3000 });

beforeAll(() => mockServer.start());
afterAll(() => mockServer.stop());
beforeEach(() => mockServer.reset());

Test that a mock has been called using hasBeenCalledWith().

test("mock has been called", async () => {
  mockServer.get("/test", { status: 200 });

  await fetch("http://localhost:3000/test");

  assert(mockServer.hasBeenCalledWith({ path: "/test" }));
});

Test that a mock has been called a specific number of times using hasBeenCalledTimes().

test("mock has been called 3 times", async () => {
  mockServer.get("/test", { status: 200 });

  await fetch("http://localhost:3000/test");
  await fetch("http://localhost:3000/test");
  await fetch("http://localhost:3000/test");

  assert(mockServer.hasBeenCalledTimes(3, { path: "/test" }));
});

Custom assertions using calls().

test("custom assertion", async () => {
  mockServer.get("/test", { status: 200 });

  await fetch("http://localhost:3000/test");

  assert(mockServer.calls().length === 1);
  assert(mockServer.calls()[0].request.path === "/test");
});

API

MockServer

constructor(options): MockServer

Create a new MockServer instance.

Param Type Default
options Options -

Example

const mockServer = new MockServer({ port: 3000 });

start(): Promise<void>

Start the mock server.

Example

await mockServer.start();

stop(): Promise<void>

Stop the mock server.

Example

await mockServer.stop();

port(): number

Get the port the mock server is running on.

Example

const port = mockServer.port();
console.log(port); // 3000

mock(matcher, response, options): MockServer

Register a mock.

Param Type Default
matcher string | RegExp | Matcher -
response string | number | Response -
options MockOptions {}

If matcher is a string or RegExp, it will be used to match the request path.
If response is a string, it will be used as the response body.
If response is a number, it will be used as the response status code.

Returns the MockServer instance.

Example

mockServer.mock({ path: "/test" }, { status: 204 });

const response = await fetch("http://localhost:3000/test");

console.log(response.status); // 204

get(matcher, response, options): MockServer

Register a mock that only responds to requests using the HTTP GET method.

Param Type Default
matcher string | RegExp | MatcherObj -
response string | number | Response -
options MockOptions {}

If matcher is a string or RegExp, it will be used to match the request path.
If response is a string, it will be used as the response body.
If response is a number, it will be used as the response status code.

Returns the MockServer instance.

Example

mockServer.get("/test", {
  status: 200,
  body: { message: "Hello World" },
});

const response = await fetch("http://localhost:3000/test");

console.log(response.status); // 200
console.log(await response.json()); // { message: "Hello World" }

post(matcher, response, options): MockServer

Register a mock that only responds to requests using the HTTP POST method.

Param Type Default
matcher string | RegExp | MatcherObj -
response string | number | Response -
options MockOptions {}

If matcher is a string or RegExp, it will be used to match the request path.
If response is a string, it will be used as the response body.
If response is a number, it will be used as the response status code.

Returns the MockServer instance.

Example

mockServer.post("/test", {
  status: 201,
  body: { message: "Hello World" },
});

const response = await fetch("http://localhost:3000/test", {
  method: "POST",
  body: JSON.stringify({ message: "Hello World" }),
});

console.log(response.status); // 201
console.log(await response.json()); // { message: "Hello World" }

put(matcher, response, options): MockServer

Register a mock that only responds to requests using the HTTP PUT method.

Param Type Default
matcher string | RegExp | MatcherObj -
response string | number | Response -
options MockOptions {}

If matcher is a string or RegExp, it will be used to match the request path.
If response is a string, it will be used as the response body.
If response is a number, it will be used as the response status code.

Returns the MockServer instance.

Example

mockServer.put("/test", {
  status: 200,
  body: { message: "Hello World" },
});

const response = await fetch("http://localhost:3000/test", {
  method: "PUT",
  body: JSON.stringify({ message: "Hello World" }),
});

console.log(response.status); // 200
console.log(await response.json()); // { message: "Hello World" }

patch(matcher, response, options): MockServer

Register a mock that only responds to requests using the HTTP PATCH method.

Param Type Default
matcher string | RegExp | MatcherObj -
response string | number | Response -
options MockOptions {}

If matcher is a string or RegExp, it will be used to match the request path.
If response is a string, it will be used as the response body.
If response is a number, it will be used as the response status code.

Returns the MockServer instance.

Example

mockServer.patch("/test", {
  status: 200,
  body: { message: "Hello World" },
});

const response = await fetch("http://localhost:3000/test", {
  method: "PATCH",
  body: JSON.stringify({ message: "Hello World" }),
});

console.log(response.status); // 200
console.log(await response.json()); // { message: "Hello World" }

delete(matcher, response, options): MockServer

Register a mock that only responds to requests using the HTTP DELETE method.

Param Type Default
matcher string | RegExp | MatcherObj -
response string | number | Response -
options MockOptions {}

If matcher is a string or RegExp, it will be used to match the request path.
If response is a string, it will be used as the response body.
If response is a number, it will be used as the response status code.

Returns the MockServer instance.

Example

mockServer.delete("/test", { status: 204 });

const response = await fetch("http://localhost:3000/test", {
  method: "DELETE",
});

console.log(response.status); // 204

mocks(): readonly Mock[]

Get all registered mocks.

Returns an array of Mock objects.

Example

mockServer.mock({ path: "/test" }, { status: 204 });

const mocks = mockServer.mocks();

console.log(mocks);
// [{ matcher: { path: "/test" }, response: { status: 204 } }]

calls(): readonly Call[]

Get all registered calls.

Returns an array of Call objects.

Example

mockServer.mock({ path: "/test" }, { status: 204 });
await fetch("http://localhost:3000/test");

const calls = mockServer.calls();

console.log(calls);
// [{ matcher: { path: "/test" }, request: <express.Request> }]

hasBeenCalledWith(matcher): boolean

Check if the route has been called with the given matcher.

Param Type Default
matcher string | RegExp | Matcher -

If matcher is a string or RegExp, it will be used to match the request path.

Returns true if the route has been called with the given matcher, false otherwise.

Example

mockServer.get("/test", { status: 200 });

console.log(mockServer.hasBeenCalledWith({ path: "/test" })); // false

await fetch("http://localhost:3000/test");

console.log(mockServer.hasBeenCalledWith({ path: "/test" })); // true

hasBeenCalledTimes(times, matcher): boolean

Check if the route has been called a certain number of times with the given matcher.

Param Type Default
times number -
matcher string | RegExp | Matcher -

If matcher is a string or RegExp, it will be used to match the request path.

Returns true if the route has been called times times with the given matcher, false otherwise.

Example

mockServer.get("/test", { status: 200 });

console.log(mockServer.hasBeenCalledTimes(0, { path: "/test" })); // true
console.log(mockServer.hasBeenCalledTimes(1, { path: "/test" })); // false

await fetch("http://localhost:3000/test");

console.log(mockServer.hasBeenCalledTimes(0, { path: "/test" })); // false
console.log(mockServer.hasBeenCalledTimes(1, { path: "/test" })); // true

countCalls(matcher): number

Count the number of times the server was called with the given matcher.

Param Type Default
matcher string | RegExp | Matcher -

If matcher is a string or RegExp, it will be used to match the request path.

Returns the number of times the server has been called with the given matcher.

Example

mockServer.get("/test", { status: 200 });

console.log(mockServer.countCalls({ path: "/test" })); // 0

await fetch("http://localhost:3000/test");

console.log(mockServer.countCalls({ path: "/test" })); // 1

reset(): void

Reset all mocks and calls.

Example

mockServer.get("/test", { status: 200 });
await fetch("http://localhost:3000/test");

console.log(mockServer.mocks());
// [{ matcher: { path: "/test", method: "GET" }, response: { status: 200 } }]

console.log(mockServer.calls());
// [{ matcher: { path: "/test", method: "GET" }, request: <express.Request> }]

mockServer.reset();

console.log(mockServer.mocks()); // []
console.log(mockServer.calls()); // []

resetMocks(): void

Reset all mocks.

Example

mockServer.get("/test", { status: 200 });

console.log(mockServer.mocks());
// [{ matcher: { path: "/test", method: "GET" }, response: { status: 200 } }]

mockServer.resetMocks();

console.log(mockServer.mocks()); // []

resetCalls(): void

Reset all calls.

Example

mockServer.get("/test", { status: 200 });
await fetch("http://localhost:3000/test");

console.log(mockServer.calls());
// [{ matcher: { path: "/test", method: "GET" }, request: <express.Request> }]

mockServer.resetCalls();

console.log(mockServer.calls()); // []

ExpectationMessage

hasBeenCalledWith(mockServer, matcher): string

Format an expectation message for hasBeenCalledWith().

Param Type Default
mockServer MockServer -
matcher Matcher -

Returns a string with the formatted expectation message.

Example

if (!mockServer.hasBeenCalledWith(matcher)) {
  throw new Error(ExpectationMessage.hasBeenCalledWith(mockServer, matcher));
}

hasBeenCalledTimes(mockServer, times, matcher): string

Format an expectation message for hasBeenCalledTimes().

Param Type Default
mockServer MockServer -
times number -
matcher Matcher -

Returns a string with the formatted expectation message.

Example

if (!mockServer.hasBeenCalledTimes(mockServer, 2, matcher)) {
  throw new Error(
    ExpectationMessage.hasBeenCalledTimes(mockServer, 2, matcher),
  );
}

Options

Object with the following properties:

Property Type Description
port number port to run the mock server on

Request

Type alias for express.Request with the body property typed as Buffer | undefined.

type Request = express.Request<{}, unknown, Buffer | undefined>;

Matcher

Type alias for MatcherObj | MatcherFn.

type Matcher = MatcherObj | MatcherFn;

MatcherObj

Object with the following properties:

Property Type Description
method string | undefined HTTP method to match against
path string | RegExp | undefined path to match against
query Request["query"] | undefined query parameters to match against.
Parameters explicitly set to undefined will not match when provided.
headers Record<string, string | undefined> | undefined headers to match against.
Headers explicitly set to undefined will not match when provided.
body string | object | undefined body to match against.
If an object is given it will be compared to the request body parsed as JSON.

MatcherFn

Function that takes a Request and returns whether the request should match.

type MatcherFn = (req: Request) => boolean;

Response

Type alias for ResponseObj | ResponseFn.

type Response = ResponseObj | ResponseFn;

ResponseObj

Object with the following properties:

Property Type Description
status number | undefined status code to respond with (defaults to 200)
headers Record<string, string> | undefined headers to respond with
body string | object | undefined body to respond with.
If an object is given it will be converted to a JSON string.
delay number | undefined delay in milliseconds before responding

ResponseFn

Function or async function that takes a Request and returns a ResponseObj.

type ResponseFn = (req: Request) => ResponseObj | Promise<ResponseObj>;

MockOptions

Object with the following properties:

Property Type Description
overwrite boolean | undefined when set to true,
previous ambiguous mocks matching the same request will be overwritten

Mock

Object with the following properties:

Property Type Description
matcher Matcher matcher to match against the request
response Response response the server will respond with when matched
options MockOptions see MockOptions

Call

Object with the following properties:

Property Type Description
request Request request the server was called with
matcher Matcher matcher the request matched against

Changelog

CHANGELOG.md

License

MIT