Important: This documentation covers Yarn 1 (Classic).
For Yarn 2+ docs and migration guide, see yarnpkg.com.

Package detail

unmock

unmock388MIT0.3.18TypeScript support: included

CircleCI codecov [![Known Vulnerabil

readme

Unmock (JavaScript SDK)

CircleCI codecov Known Vulnerabilities Chat on Gitter

Fuzz test your REST API calls.

Install

$ npm install --save-dev unmock

What Does Unmock Do

Unmock helps you fuzz test REST API calls. Fuzz testing, or fuzzing, is a form of testing where you verify the correctness of code by asserting that it behaves correctly with variable and unexpected input. The response of a REST API is most often variable and unexpected. So, in most cases, fuzz testing is a useful way to test integrations with APIs.

When To Use Unmock

Below are some questions to ask when determining if Unmock is a good fit for your development process.

  • Is my code base in JavaScript or TypeScript?
  • Does my code base have tests written in Jest, Mocha, Jasmine, Tap or Ava?
  • Do I make a network call from my codebase to a REST API?

If the answer is yes to all of these questions, Unmock could help you test code paths in your code that make REST API calls and use the responses from those API.

The Docs

If you don't like reading READMEs, check out our docs!

Usage

Here is a mock defined using unmock, a function to be tested, and a test written in jest. The commented numbers are explained in the text below this code example.

const fetch = require("node-fetch");
const unmock = require("unmock");
const { runner, transform, u } = unmock;
const { withCodes } = transform;

unmock
  .mock("https://zodiac.com", "zodiac")
  .get("/horoscope/{sign}") // 1
  .reply(200, {
    horoscope: u.string(), // 2
    ascendant: u.opt(u.string()) // 3
  })
  .reply(404, { message: "Not authorized" }); // 4

async function getHoroscope(sign) {
  // use unmock.fetch, request, fetch, axios or any similar library
  const result = await fetch("https://zodiac.com/horoscope/" + sign);
  const json = await result.json();
  return { ...json, seen: false };
}

let zodiac;
beforeAll(() => {
  zodiac = unmock.default.on().services.zodiac;
});
afterAll(() => unmock.default.off());

describe("getHoroscope", () => {
  it(
    "augments the API call with seen=false",
    runner(async () => { // 5
      zodiac.state(withCodes(200)); // 6
      const res = await getHoroscope();
      expect(res).toMatchObject(JSON.parse(zodiac.spy.getResponseBody())); // 7
      expect(res.seen).toBe(false);
    }),
  );
});

With unmock, you can (1) override a REST endpoint to provide (2) variable and (3) optional responses in addition to (4) different status codes. Then, one uses the (5) runner to automatically run a test multiple times with subtly different responses from the API. One can also (6) initialize the API to a given state and (7) make assertions about how an API was used.

Specifying hostname

The request hostname should be a string.

unmock.mock('http://www.example.com')
  .get('/resource')
  .reply(200, u.integer())

Unmock will then refer to the service as example. To specify the name of the service as foo, we would write.

unmock.mock('http://www.example.com', 'foo')
  .get('/resource')
  .reply(200, u.string())

To match multiple protocols or URLs, use associate.

unmock.default.associate('https://www2.example.com', 'foo')

Specifying the path

The request path should be a string, and you can use any HTTP verb. Wild-cards in the string should be enclosed in curly braces.

unmock.mock('http://www.example.com')
  .get('/resource/{id}')
  .reply(200, u.string())

Alternatively, you can use an array of path segments, where each segment is either a string, a string in curly-braces for an open parameter, or a key/value pair associating a name of a path parameter to a regex matching it.

unmock.mock('http://www.example.com')
  .get(["users", /[0-9]+/, "status"]) // "/users/{id}/status"
  .reply(200, u.string())

Specifying request body

You can specify the request body to be matched as the second argument to the get, post, put or delete specifications. The argument can be any valid JSON, json-schema-poet using the u syntax(u.integer(), u.string()) or any combination thereof.

Here is an example of a post request that will only validate if it contains a token.

unmock.mock('http://www.example.com')
  .post('/login', u.type({ token: u.string()}, {expires: u.integer()}))
  .reply(200, { id: u.string() })

Unmock does not support body encodings other than "application/json" at this point.

Specifying request query string

Unmock automatically ignores query strings. However, it understands query strings if you would like to match against them.

When query strings are included in Unmock, they will act as if they are required. Most APIs do not have required query strings, so make sure to double-check the API documentation before indicating a required query string.

These parameters can be included as part of the path:

unmock.mock('http://example.com')
  .get('/users?foo=bar')
  .reply(200)

Instead of placing the entire URL, you can specify the query part as an object:

unmock.mock('http://example.com')
  .get('/users')
  .query({ name:u.string(), surname: u.string() })
  .reply(200, { results: u.array({id: u.integer() }) })

Specifying Request Headers

You can specify the request headers like this:

unmock.mock('http://www.example.com', {
  reqheaders: {
    authorization: 'Basic Auth',
  },
})
  .get('/')
  .reply(200)

Or you can use a regular expression to check the header values.

unmock.mock('http://www.example.com', {
  reqheaders: {
    'X-My-Awesome-Header': /Awesome/i,
  },
})
  .get('/')
  .reply(200)

Headers in unmock are always a partial match, meaning that additional headers are ignored. This means that you don't need to worry about matching against common headers like Content-Type and Host.

Specifying replies

You can specify the return status code for a path on the first argument of reply like this:

unmock.mock('http://myapp.iriscouch.com')
  .get('/users/1')
  .reply(404)

You can also specify the reply body as valid JSON, json-schema-poet, or any combination thereof.

unmock.mock('http://www.google.com')
  .get('/')
  .reply(200, u.stringEnum(['Hello from Google!', 'Do no evil']))

If you would like to transform any part of a constant reply (ie a fixture recorded from real API traffic) into a variable version of itself, use u.fuzz. This command infers the type of its input and produces output following the same schema.

unmock.mock('http://www.foo.com')
  .get('/')
  // produces { firstName: "a random string", lastName: "another random string" }
  .reply(200, u.fuzz({ firstName: "Bob", lastName: "Smith" }))

Specifying reply headers

You can specify the reply headers like this:

unmock.mock('https://api.github.com')
  .get('/repos/atom/atom/license')
  .reply(200, { license: 'MIT' }, { 'X-RateLimit-Remaining': u.integer() })

Chaining

You can chain behavior like this:

unmock.mock('http://myapp.iriscouch.com')
  .get('/users/1')
  .reply(404)
  .post('/users', {
    username: u.string(),
    email: u.string(),
  })
  .reply(201, {
    ok: u.boolean(),
    id: u.string(),
    rev: u.string(),
  })
  .get('/users/123ABC')
  .reply(200, {
    _id: u.string(),
    _rev: u.string(),
    user: u.string(),
    name: u.string()
  })

Ignorable API calls

For ignorable API calls where you are passing through information but don't care about a response, you can instruct unmock to serve random 200 responses to those requests using tldr.

unmock
  .mock("https://my-analytics-api.vendor.com)
  .tldr();

Expectations

Unmock uses sinon spies to help you compose (great) expectations.

In general, try to write expectations using spies instead of hardcoded values. Instead of expect(res).toEqual({foo: "bar", baz: true }), favor expect(res).toEqual({ ...spy.getRequestBody(), baz: true })

Assuming you have defined a service using unmock, you can use spies following the <verb><Request|Response><Thing> convention. For example, getResponseBody or deleteRequestPath. Nonsensical things like getRequsetBody are not defined.

test("analytics API was called", async () => {
  await myFunction()
  expect(analyticsService.spy.postRequestBody())
    .toMatchObject({ event: "VISITED" })
})

The following getter functions are defined on the spy object.

  • getRequestPathname
  • getRequestPath
  • getRequestHeaders
  • getRequestQuery
  • getRequestProtocol
  • getResponseBody
  • getResponseCode
  • getResponseHeaders
  • postRequestBody
  • postRequestPathname
  • postRequestPath
  • postRequestHeaders
  • postRequestQuery
  • postRequestProtocol
  • postResponseBody
  • postResponseCode
  • postResponseHeaders
  • putRequestBody
  • putRequestPathname
  • putRequestPath
  • putRequestHeaders
  • putRequestQuery
  • putRequestProtocol
  • putResponseBody
  • putResponseCode
  • putResponseHeaders
  • deleteRequestPathname
  • deleteRequestPath
  • deleteRequestHeaders
  • deleteRequestQuery
  • deleteRequestProtocol
  • deleteResponseBody
  • deleteResponseCode
  • deleteResponseHeaders

In addition, spies are full-fledged sinon spies. More about their usage in Unmock can be found here, and more information on sinon can be found here.

Initializing Mocks

Unmock supports the initialization of services to arbitrary states. This is helpful, for example, if you want to test how your code behaves when a given service returns exactly five items or when a particiular field in an object is missing or present.

To do this, set the state property of a service. The state property is a function that takes a request and an OpenAPI schema as input and returns an OpenAPI schema and output. Many utility functions have been created for the most common state manipulations. For example, to test the outcome with only certain codes, use withCodes.

const unmock = require('unmock');
const withCodes = unmock.transform.withCodes;
const github = unmock.default.on().services.github;
github.state(withCodes([200, 201, 404]));

Because withCodes returns a function, the same thing could have been written.

const unmock = require('unmock');
const withCodes = unmock.transform.withCodes;
const github = unmock.default.on().services.github;
github.state((req, schema) => withCodes([200, 201, 404])(req, schema));

This is useful, for example, if you want to test a certain code only if a given header is present.

const unmock = require('unmock');
const withCodes = unmock.transform.withCodes;
const github = unmock.default.on().services.github;
github.state((req, schema) =>
  withCodes([200, ...(req.headers.MyHeader ? [404] : [])])(req, schema));

The unmock documentation contains more information about initializing the state.

Faker API

UnmockFaker class provides a lower-level API for working with mocks. You can create a new UnmockFaker via unmock.faker():

const unmock, { Service, ISerializedRequest} = require("unmock");
// import unmock from "unmock";  // ES6

const faker = unmock.faker();

To use the faker for mocking, you need to add services. The first option is to use the nock method:

faker
  .mock("http://petstore.swagger.io", "petstore")
  .get("/v1/pets")
  .reply(200, { foo: u.string() });

Alternatively, you can create a service from OpenAPI specification with Service.fromOpenAPI:

const { Service } = require("unmock");

const schema: OpenAPIObject = ...; // Load OpenAPIObject
const petstoreService = Service.fromOpenAPI({ schema, name: "petstore" })

// Add service to `faker`
faker.add(petstoreService);

You can then also modify the state of the petstore service via state property:

const { transform } = require("unmock");
// Service should always return code 200
petstore.state(transform.withCodes(200));

Once you have added a service, you can use faker.generate method to create a mock for any Request object:

const { UnmockRequest, UnmockResponse } = require("unmock");

const req: UnmockRequest = {
  host: "petstore.swagger.io",
  protocol: "http",
  method: "get",
  path: "/v1/pets",
  pathname: "/v1/pets",
  headers: {},
  query: {},
}

const res: UnmockResponse = faker.generate(req);

// Access res.statusCode, res.headers, res.body, etc.
expect(res.statusCode).toBe(200);

Runner

The unmock runner runs the same test multiple times with different potential outcomes from the API. All of your unmock tests should use the runner unless you are absolutely certain that the API response will be the same every time.

const { runner } = "unmock";
test("my API always works as expected", runner(async () => {
  const res = await myApiFunction();
  // some expectations
}));

OpenAPI

Unmock supports the reading of OpenAPI specifications out of the box. Place your specification in a folder at the root of your project called __unmock__/<myspecname>, where <myspecname> is the name of the spec on the unmock.on().services object. Several examples of this exist on the internet, most notably here.

Tutorials

Contributing

Thanks for wanting to contribute! Take a look at our Contributing Guide for notes on our commit message conventions and how to run tests.

Please note that this project is governed by the Meeshkan Community Code of Conduct. By participating in this project, you agree to abide by its terms.

Development

  • See publish.md for instructions how to make a new release

License

MIT

Copyright (c) 2018–2019 Meeshkan and other contributors.

changelog

CHANGELOG.md

0.3.17 (2020-04-16)

Fixes:

  • Fixes the adding of erroneous response codes for certain endpoints.
  • Fixes transformations when multiple endpoints are used.

0.3.16 (2020-03-12)

Features:

  • Removes useInProduction.

0.3.15 (2020-03-01)

Features:

  • Adds unmock-xmlhttprequest.

0.3.14 (2020-02-12)

Fixes:

  • Fix import type error that made Unmock fail to compile

0.3.13 (2020-02-11)

Features:

  • Add programmatic Faker API for creating and managing services
  • Add stop command to unmock-server
  • Extract runner from unmock-core
  • Add new unmock-runner package with jestRunner for Jest configuration

0.3.12 (2020-01-09)

Fixes:

  • Fixed bug in unmock-server CLI by including oclif.manifest.json in the package.

Features:

  • Updated packages to fix security vulnerabilities

0.3.11 (2019-12-19)

Features:

  • Add CLI to unmock-server
  • Add unmock-browser package, which runs unmock-js in browser and React Native
  • Implement parsing of request headers in unmock-fetch
  • Add unmock-types package
  • Extract UnmockFaker class from Backend class
  • Rename unmock.newFaker() to unmock.faker()

Fixes:

  • Remove concurrently dependency

0.3.10 (2019-11-4)

Features:

  • Add unmock-server, a mock server
  • Add unmock.randomize.on() option for randomizing responses
  • Introduce unmock CLI in unmock-cli package

0.3.9 (2019-10-17)

Features:

  • Extract unmock-node from unmock-core for better portability to other environments
  • Add unmock-fetch package

0.3.8 (2019-9-27)

Features:

  • Request and response include bodyAsJson for convenient access to a parsed object
  • Include request headers in Jest reporter and redact Authorization fields
  • Adds u.opt

Fixes:

  • Fix missing Faker issue #272
  • Adds certain types as a dependency

0.3.7 (2019-9-26)

Fixes:

  • Fixes a bug in the reporter where no test suites were shown

0.3.6 (2019-9-26)

Fixes:

  • fixes a bug where path parameters were incorrectly matched if they were anything other than a string.

0.3.5 (2019-9-25)

Features:

  • the runner no longer needs manual resetting of the spies

Fixes:

  • json-schema-faker ignored minItems, which is now disabled. as a result, there is a slight regression in the fuzzing (it is less fuzzy) that we can address in a future release.

0.3.4 (2019-09-22)

Features:

  • nock-like syntax for setting request headers, response headers, request bodies, and queries

0.3.3 (2019-09-20)

Features:

  • Added more randomization to test runner
  • Added more accessors to spy methods, for example, postResponseCode
  • Added unmock.associate method for associating a service with a URL
  • Changed Jest reporter to show only one test suite at a time

0.3.2 (2019-09-18)

Features:

  • Added mapDefaultTo to the transformer in order to map a default OpenAPI response to another code.
  • Made it possible for JSON schema objects created by u to contain plain JSON objects that will be turned into constants.

Fixes:

  • Made prettier work in each repo of the monorepo, resulting in a lot of changes. The whole thing is more readable now.

0.3.1 (2019-09-18)

Breaking changes:

  • Renamed unmock.gen package as unmock.transform
  • Renamed unmock.transform.<method>.address package as unmock.transform.<method>.lens

0.3.0 (2019-09-17)

Features:

  • New state mananagement system using monocle-ts and functional programming conventions. To set the state of any service, you use a function that accepts a request and an OpenAPI schema and returns a modified schema.
  • A new runner for fuzz testing.

0.2.2 (2019-09-16)

Fixes:

  • Disabled esModuleInterop in tsconfig.json so that the package can be used without esModuleInterop

Features:

  • Added initial nock-like syntax for declaring services
  • Added unmock-jest package for Unmock Jest reporter
  • Added snapshotting of requests to local disk so they're available for the Jest reporter

0.2.1 (2019-08-30)

Breaking changes:

  • Renamed unmock-node package as unmock

Fixes:

  • Added the missing SinonJS types
  • Fixed setting state using reserved OpenAPI schema keywords such as type

Features:

  • Added pathname and query request object
  • Added spy helpers such as postResponseBody

0.2.0 (2019-08-23)

Breaking changes:

  • unmock.on() returns this instead of states object.
  • Deleted unmock.states: state changes should be done via unmock.services["service"].state

Features:

  • Exports UnmockRequest, UnmockResponse and other exposed types
  • Adds service spy for keeping track of service calls
  • Exports sinon for easy assertions on service spy

0.1.10 (2019-08-14)

Fixes:

  • Remove polyfills polluting global scope

0.1.9 (2019-08-13)

Fixes:

  • Add missing core-js dependency to packages

0.1.8 (2019-08-12)

Features:

  • Package compilation with Babel to support Node.js >= 6
  • Better formatting for error messages
  • Read services from node_modules/@unmock in addition to __unmock__

0.1.7 (2019-08-07)

Features:

  • Export types Request and States
  • Add typing to states object

Fixes:

  • Fix issue in Node<10 where URL was used without it being available

0.1.6 (2019-08-06)

Features:

  • Add allowedHosts object to unmock to add new allowed hosts
  • Add flaky mode with unmock.flaky.on()
  • Add better error messages when no matching operation is found from the specification
  • Allow changing service state with functions taking the request as an argument

Fixes:

  • Fix bugs in loas3 in handling references

0.1.5 (2019-07-26)

Fix:

  • dist/ directory was missing from the release because of a bug in the CI/CD pipeline #107

0.1.4 (2019-07-25)

Features:

  • Setting states is now a typed experience. Hints on how to use the state facade object are available -> ba6a78f.
    • As services are dynamically loaded, hints for available services do not show up. You may use the services dynamically as usual though.
  • textResponse is now infered automatically when setting a state with a string -> bd9ac1
    • If you need to specify additional DSL instructions, you still need to use textResponse.

Fix:

  • Dereferencing JSON $ref instructions only happens when needed (setting a state) -> 36cf981
    • Dereferencing certain services took long otherwise and was redundant work.
    • For the time being, only dereferences internal references and references to local files.
  • Renames textMW transformer to textResponse -> 3c43358
  • Import notation no longer requires an asterisk (import unmock from "unmock-node") -> cdd46ee
  • middleware renamed to dsl, with objResponse as a named export instead of the default (import { dsl } from "unmock-node") -> cdd46ee

Bugfixes:

  • Replace server path prefix (terminated with a /) with /, instead of removing the non-terminated path prefix -> 30083e5
    • When a service path prefix and an endpoint shared the same prefix, setting a state would break (e.g. consider Slack's /api/ service prefix and /api.test endpoint).

0.1.3 (2019-07-19)

Bugfixes:

  • Generator no longer uses the default values for consecutive generation calls -> 30e1519
    • The use of default values is set for header generation and was not cancelled before body generation.

0.1.2 (2019-07-19)

Features:

  • Resolve JSON $ref when loading a service -> 8cff0bf

0.1.1 (2019-07-18)

Bugfixes:

  • If the required field is not stated in the response specification, automatically generate all the fields in the matching response. If it is present, randomly select which additional fields to generate -> a414f89

0.1.0 (2019-07-18)

Initial release.
Features:

  • (OpenAPI) Specification-based automatic mocking system.
  • Interception of requests with a NodeJS backend client - your test code requests won't reach 3rd party APIs by mistake!
  • Basic options to control whitelisted URLs (supports wildcards and regular expressions).
  • State management system (user may supply their own values to generate matching responses from the specification).
    • Users may set states with object notation ({ users: { social: { facebook: "facebook_id" }}}) and textual responses (for plain/text endpoints) using textResponse (textResponse("foobar")). These are found under the exported middleware object. Refer to documentation for more examples and clarification.
    • Fluent API access to set the states, supporting wildcard matches, REST methods, custom service names, etc.
    • States are applied to endpoints for which they are valid. If no endpoint is specified, all endpoints and all REST methods are checked for the given state.
    • Runtime error for mismatching values, attempting to set states for non-existing endpoints, etc.
  • Basic unmock DSL to allow fine-tuned modification of mocked responses ("states").
    • $code (to specify response status code), $times (to specify number of times a state is valid) and $size (to specify the size of an array).
  • Boilerplate code for future expansion into jsdom and future CLI support.