Mock HTTP APIs for rapid development and reliable testing.
Table of Contents
- How does it work?
- Install
- Usage
When Server Mockr receives a request it matches the request against all active scenarios and expectations that have been configured. It will verify the request if needed and respond as configured in the expectation. When no matching expectation is found, the mock server will return a 404.
Server Mockr will spin up two servers on startup: the mock server and the control server. The control server has a GUI and a REST API that can be used to control the mock server. It can be used to view, start and stop scenarios or retrieve request logs.
$ npm install server-mockr --save-dev
You can setup a mock server like this:
const { ServerMockr } = require("server-mockr");
const mockr = new ServerMockr();
mockr.when("/todos/1").respond({ id: 1, completed: true });
mockr.start();
This setup says that we will match every HTTP call to /todos/1
and respond with a status 200 JSON response.
By default the mock server is available at http://localhost:3001 and the control server at http://localhost:3002.
Using a string:
mockr.when("/resource").respond("ok");
Using a string with parameters:
mockr.when("/resources/:id").respond("ok");
Using a regular expression:
mockr.when(/\.html$/).respond("ok");
Using a request matcher:
mockr.when(request("/resource")).respond("ok");
Using the path method on a request matcher:
mockr.when(request().path("/resource")).respond("ok");
Using the path method on a request matcher with a value matcher:
mockr.when(request().path(startsWith("/res"))).respond("ok");
Using the path method on a request matcher with a custom value matcher:
mockr.when(request().path(path => path.includes("todos"))).respond("ok");
Request path parameters can be matched by using the param
method:
mockr.when(request("/resources/:id").param("id", "1")).respond("ok");
The request method can be specified by using the method
method or the with the get,post,put,delete
shortcut methods.
Using the method
method:
mockr.when(request("/resource").method("POST")).respond("ok");
Using a shortcut method:
mockr.when(request().post("/resource")).respond("ok");
The request body can be specified by using the body
method.
Body with exact matcher:
mockr
.when(
request()
.post("/resources")
.body({ firstName: "First", lastName: "Last" })
)
.respond({ id: 1, firstName: "First", lastName: "Last" });
Body with partial matcher:
mockr
.when(
request()
.post("/resources")
.body(matchesObject({ firstName: "First" }))
)
.respond({ id: 1, firstName: "First", lastName: "Last" });
Body with property matcher:
mockr
.when(
request()
.post("/resources")
.body(prop("firstName", startsWith("F")))
)
.respond({ id: 1, firstName: "First", lastName: "Last" });
Body with multiple matchers:
mockr
.when(
request()
.post("/resources")
.body(prop("firstName", startsWith("F")))
.body(prop("lastName", startsWith("L")))
)
.respond({ id: 1, firstName: "First", lastName: "Last" });
Body with custom matcher:
mockr
.when(
request()
.post("/resources")
.body(body => body.firstName.startsWith("F"))
)
.respond({ id: 1, firstName: "First", lastName: "Last" });
The request query string can be specified by using the query
method.
Query string with single parameter (/resources?limit=100
):
mockr
.when(
request()
.get("/resources")
.query("limit", "100")
)
.respond("ok");
Query string with array parameter (/resources?id=1&id=2
):
mockr
.when(
request()
.get("/resources")
.query("id", ["1", "2"])
)
.respond("ok");
Query string with multiple parameters (/resources?limit=100&order=asc
):
mockr
.when(
request()
.get("/resources")
.query("limit", "100")
.query("order", "asc")
)
.respond("ok");
Query string with multiple paramters and matchers (/resources?limit=99&order=asc
):
mockr
.when(
request()
.get("/resources")
.query("limit", matchesRegex(/[0-9]+/))
.query("order", anyOf("asc", "desc"))
)
.respond("ok");
Query string with custom matcher:
mockr
.when(
request()
.get("/resources")
.query(query => query.limit === "100")
)
.respond("ok");
Request headers can be specified by using the header
method.
Single header:
mockr
.when(
request()
.get("/resources")
.header("Authorization", "token")
)
.respond("ok");
Multiple headers:
mockr
.when(
request()
.get("/resources")
.header("Authorization", "token")
.header("Accept-Language", includes("nl-NL"))
)
.respond("ok");
Cookies can be specified by using the cookie
method.
mockr
.when(
request()
.get("/resources")
.cookie("sessionId", "id")
)
.respond("ok");
Files can be specified by using the file
method.
mockr
.when(
request()
.get("/resources")
.file("image", {
fileName: "image.png",
mimeType: "image/png",
size: 144
})
)
.respond("ok");
The url
method allows you to match on the exact url:
mockr.when(request().url("/resources?limit=100&order=asc")).respond("ok");
Using the respond
method to specify the response status code:
mockr.when("/resource").respond(404);
Using the respond
method to specify the response status code and body:
mockr.when("/resource").respond(404, "Not Found");
Use the status
method to specify the response status code:
mockr.when("/resource").respond(response().status(404));
Using a string, will respond with a status 200 text response:
mockr.when("/resource").respond("ok");
Using an object, will respond with a status 200 JSON response:
mockr.when("/resource").respond({ id: 1 });
Using a response builder, will respond with a status 200 text response:
mockr.when("/resource").respond(response("match"));
Using a response builder with the body
method:
mockr.when("/resource").respond(response().body("match"));
Using a custom function, this can be useful to inject request values:
mockr
.when("/resources/:id")
.respond(({ req }) => response({ id: req.params.id });
Using a promise:
mockr
.when("/resources/:id")
.respond(({ req }) => {
const resource = await fetchResource(req.params.id);
return response(resource);
});
Use the header
method to specify response headers:
mockr
.when("/resource")
.respond(response("ok").header("Cache-Control", "no-cache"));
Use the cookie
method to specify response cookies:
mockr.when("/resource").respond(response("ok").cookie("sessionId", "ID"));
Cookie options can be set with an additional argument:
mockr
.when("/resource")
.respond(response("ok").cookie("sessionId", "ID", { httpOnly: true }));
Use the delay
method to delay a response in miliseconds:
mockr.when("/resource").respond(response("ok").delay(1000));
Use the second argument to specify a minimum and maximum delay:
mockr.when("/resource").respond(response("ok").delay(1000, 2000));
Use the redirect
method specify a 302 redirect:
mockr.when("/resource").respond(response().redirect("/new-resource"));
Use the proxy
method to proxy requests to a real server:
mockr
.when(request().url(startsWith("/some-api")))
.respond(response().proxy("https://example.com"));
Use the proxyRequest
builder to override request values:
mockr
.when("/some-api/test")
.respond(
response().proxy(
proxyRequest("https://example.com").path("/other-api/test")
)
);
Use the times
method to specify how many times an expectation should match:
mockr
.when("/resource")
.times(1)
.respond("First time");
mockr
.when("/resource")
.times(1)
.respond("Second time");
It is possible to set response behaviours for all responses using the next
method.
When calling the next
method, the expectation will not respond.
Instead, it will set the specified response behaviour and proceed to the next matching expectation.
mockr
.when("*")
.respond(response().header("Access-Control-Allow-Origin", "*"))
.next();
mockr.when("/resource").respond("match with cors");
Using the verify
method, it is possible to verify if a matched request, matches certain conditions.
By default, the mock server will return a status 400 JSON response containing the validation error.
It is possible to override the default response with the verifyFailedRespond
method.
Default verify, will respond with a status 400 JSON response containing the verification error:
mockr
.when(request().post("/resources"))
.verify(request().body({ firstName: "First" }))
.respond("ok");
Verify with custom response:
mockr
.when(request().post("/resources"))
.verify(request().body({ firstName: "First" }))
.verifyFailedRespond(response("Server Error").status(500))
.respond("ok");
Conditional verification:
mockr
.when(request().post("/resources"))
.verify(({ req }) =>
req.headers["no-validate"] ? true : request().body({ firstName: "First" })
)
.respond("ok");
Actions are actions that can be taken after the mock server responded to some expectation.
The setState
action can be used to change state.
This can be useful to simulate stateful web services.
// Respond with empty todos list
mockr.when(request().get("/todos"), state("todos", undefined)).respond([]);
// Respond with filled todos list
mockr
.when(request().get("/todos"), state("todos", "saved"))
.respond([{ id: 1 }]);
// Set todos to "saved"
mockr
.when(request().post("/todos"))
.respond({ id: 1 })
.afterRespond(setState("todos", "saved"));
The sendRequest
action can be used to trigger a HTTP request.
This can be useful to simulate webhooks.
scenario
.when(request().post("/todos"))
.respond({ id: 1 })
.afterRespond(
sendRequest("https://example.com/webhook")
.header("X-Signature", "f9e91a6f0462b4c61e3667a4f4a6e7d02edfa518")
.body({ action: "addedPost", post: { id: 1 } })
);
The delay
action can be used to delay other actions.
This can be useful to trigger a webhook after some amount of time.
scenario
.when(request().post("/todos"))
.respond({ id: 1 })
.afterRespond(
delay(
sendRequest("https://example.com/webhook")
.header("X-Signature", "f9e91a6f0462b4c61e3667a4f4a6e7d02edfa518")
.body({ action: "addedPost", post: { id: 1 } }),
5000
)
);
Scenarios can be used to group expectations together.
They can be started and stopped programmatically, with the GUI or with the REST API.
Scenarios also contain individual state, which is useful when simulating stateful web services.
When a scenario is started, a scenario runner is created.
A scenario can have multiple runners if the multipleScenarioRunners
option is set to true
.
const scenario = mockr.scenario("todo-scenario");
// Respond with empty todos list
scenario.when(request().get("/todos"), state("todos", undefined)).respond([]);
// Set todos to "saved"
scenario
.when(request().post("/todos"))
.respond({ id: 1 })
.afterRespond(setState("todos", "saved"));
// Respond with todos list
scenario
.when(request().get("/todos"), state("todos", "saved"))
.respond([{ id: 1 }]);
A scenario can be started in a few different ways.
Using the GUI:
Open a browser and navigate to the control server (by default http://localhost:3001). Click on the start button to start a scenario.
Using the REST API:
POST http://localhost:3001/api/scenarios/{scenarioID}/scenario-runners
Using the REST API with default state:
POST http://localhost:3001/api/scenarios/{scenarioID}/scenario-runners?state[todos]=saved
Using the JS API:
mockr
.scenario("todo-scenario")
.when(request().get("/todos"))
.respond([]);
mockr.scenarioRunner("todo-scenario");
Using the JS API with default state:
mockr
.scenario("todo-scenario")
.when(request().get("/todos"), state("todos", "saved"))
.respond([{ id: 1 }]);
mockr.scenarioRunner("todo-scenario", { state: { todos: "saved" } });
Scenarios can be dynamically configured on startup with the onStart
callback.
This can be useful to create data on the fly or to conditionally add expectations based on some state.
mockr
.scenario("user-scenario")
.config("userId", {
type: "string",
default: "000-000-000-001"
})
.onStart(({ config, scenario }) => {
const user = createUser(config.userId);
scenario.when(request().get("/user")).respond(user);
});
Bootstrapping can be used to bootstrap a client.
This can be useful if you for example want to redirect a browser to the application under test with certain parameters:
mockr
.scenario("todo-scenario")
.onBootstrap(ctx =>
response().redirect(`https://example.com/${ctx.config.locale}/todos`)
)
.when(request().get("/todos"))
.respond([{ id: 1 }]);
The client can then be bootstrapped by navigating the client to the following address:
GET http://localhost:3001/api/scenarios/{scenarioID}/scenario-runners/create-and-bootstrap?config[locale]=nl-nl
Matchers are functions which can be used to match values.
The allOf
matcher can be used to check if some value matches all given matchers:
mockr
.when(request().path(allOf(startsWith("/static"), endsWith(".png"))))
.respond("ok");
The anyOf
matcher can be used to check if some value matches any given matcher:
mockr
.when(request().path(anyOf(endsWith(".jpg"), endsWith(".png"))))
.respond("ok");
Or when given values:
mockr.when(request().query("order", anyOf("asc", "desc"))).respond("ok");
The endsWith
matcher can be used to check if a string ends with some suffix:
mockr.when(request().path(endsWith(".html"))).respond("ok");
The includes
matcher can be used to check if a string includes some other string:
mockr.when(request().path(includes("todo"))).respond("ok");
The isEqualTo
matcher can be used to check if a value is equal to some other value:
mockr.when(request().path(isEqualTo("/todos"))).respond("ok");
The isGreaterThan
matcher can be used to check if a number is greater than some other number:
mockr.when(request().body(prop("count", isGreaterThan(5)))).respond("ok");
The isGreaterThanOrEqual
matcher can be used to check if a number is greater than or equal to some other number:
mockr
.when(request().body(prop("count", isGreaterThanOrEqual(5))))
.respond("ok");
The isLowerThan
matcher can be used to check if a number is lower than some other number:
mockr.when(request().body(prop("count", isLowerThan(5)))).respond("ok");
The isLowerThanOrEqual
matcher can be used to check if a number is lower than or equal to some other number:
mockr.when(request().body(prop("count", isLowerThanOrEqual(5)))).respond("ok");
The matchesObject
matcher can be used to check if an object partially matches some other object:
mockr.when(request().body(matchesObject({ id: 1 }))).respond("ok");
The matchesRegex
matcher can be used to check if a string matches some regex:
mockr
.when(request().header("Authorization", matchesRegex(/[a-z0-9]+/)))
.respond("ok");
The not
matcher can be used to negate other matchers:
mockr.when(request().path(not(startsWith("/res")))).respond("ok");
When given a value, it will check if the value is not equal to:
mockr.when(request().path(not("/res"))).respond("ok");
The oneOf
matcher can be used to check if some value matches exactly one of the given matchers:
mockr
.when(request().path(oneOf(startsWith("/static"), endsWith(".png"))))
.respond("ok");
The pointer
matcher can be used to check if the value referenced by the pointer matches some matcher:
mockr
.when(request().body(pointer("/addresses/0/street", includes("Street"))))
.respond("ok");
The prop
matcher can be used to check if a property value matches some matcher:
mockr.when(request().body(prop("firstName", startsWith("F")))).respond("ok");
The startsWith
matcher can be used to check if a string starts with some prefix:
mockr.when(request().path(startsWith("/res"))).respond("ok");
All requests and responses are logged and can be retrieved as HTTP Archive / HAR.
Using the JS API:
const har = mockr.getHAR();
Using the REST API:
GET http://localhost:3001/api/logging/har
The ServerMockr
class accepts the following options:
const { ServerMockr } = require("server-mockr");
const mockr = new ServerMockr({
controlServerPort: 6001,
mockServerPort: 6002,
multipleScenarioRunners: true,
globals: { globalValue: "value" }
});