server-mockrs
TypeScript icon, indicating that this package has built-in type declarations

1.1.6 • Public • Published

Server Mockr

Mock HTTP APIs for rapid development and reliable testing.

Table of Contents

How does it work?

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.

Install

$ npm install server-mockr --save-dev

Usage

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.

Specifying requests

Request path

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 params

Request path parameters can be matched by using the param method:

mockr.when(request("/resources/:id").param("id", "1")).respond("ok");

Request method

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");

Request body

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" });

Request query string

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

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");

Request cookies

Cookies can be specified by using the cookie method.

mockr
  .when(
    request()
      .get("/resources")
      .cookie("sessionId", "id")
  )
  .respond("ok");

Request files

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");

Request url

The url method allows you to match on the exact url:

mockr.when(request().url("/resources?limit=100&order=asc")).respond("ok");

Specifying responses

Response status

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));

Response body

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);
  });

Response headers

Use the header method to specify response headers:

mockr
  .when("/resource")
  .respond(response("ok").header("Cache-Control", "no-cache"));

Response cookies

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 }));

Response delay

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));

Response redirect

Use the redirect method specify a 302 redirect:

mockr.when("/resource").respond(response().redirect("/new-resource"));

Response proxy

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")
    )
  );

Specifying times

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");

Specifying behaviours for all responses

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");

Verifying requests

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

Actions are actions that can be taken after the mock server responded to some expectation.

setState action

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"));

sendRequest action

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 } })
  );

delay action

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

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 }]);

Starting scenarios

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" } });

Configurable scenarios

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

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

Matchers are functions which can be used to match values.

allOf

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");

anyOf

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");

endsWith

The endsWith matcher can be used to check if a string ends with some suffix:

mockr.when(request().path(endsWith(".html"))).respond("ok");

includes

The includes matcher can be used to check if a string includes some other string:

mockr.when(request().path(includes("todo"))).respond("ok");

isEqualTo

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");

isGreaterThan

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");

isGreaterThanOrEqual

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");

isLowerThan

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");

isLowerThanOrEqual

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");

matchesObject

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");

matchesRegex

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");

not

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");

oneOf

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");

pointer

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");

prop

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");

startsWith

The startsWith matcher can be used to check if a string starts with some prefix:

mockr.when(request().path(startsWith("/res"))).respond("ok");

Logging

HAR

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

Options

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" }
});

Dependents (0)

Package Sidebar

Install

npm i server-mockrs

Weekly Downloads

1

Version

1.1.6

License

MIT

Unpacked Size

881 kB

Total Files

202

Last publish

Collaborators

  • amit034