@teamjourney/api-mock-server
v0.2.6
Published
API mocking library that runs as a real HTTP server
Downloads
1,644
Readme
API Mock Server
API mocking library that runs as a real HTTP server in Node.js
API Mock Server can be used to test code that performs HTTP requests without calling the real service and allows full control over the request and responses.
Table of Contents
- How it works
- Install
- Usage
- Current Limitations
- Credits
- License
How it works
API Mock Server runs on a real port and responds to real HTTP requests. This means that it is ideal for testing or simulating HTTP requests made by any language or system without interacting with its code directly.
This approach differs to projects such as nock
which work by overriding Node's http.request
function.
Control of the mock is handled in Node.js and works well with testing frameworks such as Mocha or Jasmine.
Install
npm install --save-dev @teamjourney/api-mock-server
or
yarn add --dev @teamjourney/api-mock-server
Node version support
Tested on Node.js 10.x and 12.x
Usage
NodeJS
const server = require('@teamjourney/api-mock-server').default;
server.start(9001)
.then(() => {
server.mock(
{ path: '/my-endpoint' },
{ body: { data: 'something' } },
);
// Call GET http://localhost:9001/my-endpoint here
// It will return with a JSON body of { data: 'something' }
server.stop();
});
ES6
import server from '@teamjourney/api-mock-server';
const init = async () => {
await server.start(9001);
server.mock(
{ path: '/my-endpoint' },
{ body: { data: 'something' } },
);
// Call GET http://localhost:9001/my-endpoint here
// It will return with a JSON body of { data: 'something' }
server.stop();
};
init();
Multiple instances (optional)
It's possible (but optional) to create multiple instances of the mock server running on different ports, each with their own routes and logging.
Use instances of the MockServer
class to achieve this. The below example is
in ES6 for simplicity.
import { MockServer } from '@teamjourney/api-mock-server';
const server = new MockServer();
MockServer
has the same interface as the core library functions so all of the
below functions should work.
Starting the server
Calling start
with a port number allows you to specify a port.
server.start(9002)
Calling start
without a port will attempt to start on a random port. It
returns a promise which resolves to http.Server
object. This allows for
finding out the port it is being run on.
server.start()
.then((httpServer) => {
const { port } = serverInfo.address();
...
});
Stopping the server
Calling stop
will stop the server.
server.stop();
Resetting the server
Calling reset
will keep the server running but will clear all mocks and logged
requests.
server.reset();
It's also possible to reset specific mocks by passing an array to reset
.
server.reset([ { path: '/my-endpoint' } ]);
This will remove the mock as well as removing it from the uncalled mocks list. If the mock has been called however, it will still appear in the called mocks list.
Trying to reset a mock that doesn't exist will fail silently.
Defining mocks
Mocking requests and responses simply involves calling mock
with two
arguments. The first defines the request shape and the second the response
shape.
The simplest call would be
server.mock({ path: '/my-endpoint' });
This would handle any GET requests (the default method) to /my-endpoint
and
return am empty 200 response (the default response status).
There are numerous ways to configure specific behaviours that are defined below.
Unmocked requests
Any unmocked requests will return an empty 501 (Not Implemented) response.
Duplicate mocks
Calling mock
with a request shape that matches an already mocked request will
throw an error.
server.mock({ path: '/my-endpoint' });
server.mock({ path: '/my-endpoint' });
Will throw an error with the following message:
Request matching {
"method": "GET",
"path": "/my-endpoint"
} already mocked
Mocking requests
Specifying paths
The path
is the only required property when defining the request shape.
server.mock({ path: '/my-endpoint' });
Specifying HTTP methods
Providing a method
will match only requests using that method. If no method
is specified and the request has a body (see below) then the method defaults to
POST, otherwise it defaults to GET.
server.mock({ path: '/my-endpoint', method: 'DELETE' });
The method
string is case-insensitive.
Supported methods are GET
, POST
, PUT
, DELETE
and HEAD
.
Multiple mocks defined on the same endpoint with different methods are treated as independent mocks.
Specifying query strings
Query strings can be mocked in 2 ways; either by adding the query string to the
path field or by providing a separate object on the query
field.
server.mock({ path: '/my-endpoint?page=1&perPage=20' });
or
server.mock({ path: '/my-endpoint', query: { page: '1', perPage: '20' } });
In the second style, the object values should always be specified as strings.
If no query string is specified in the mock then any request to that endpoint will match, regardless of the query string.
Specifying request bodies
Request bodies can be mocked by providing the body
property. Deeply nested
object structures will be matched recursively.
server.mock({ path: '/my-endpoint', body: { foo: 'bar' } });
The data types of the mock and the request fields need to match exactly. This is by design, as many real servers are sensitive to this.
If a request body is specified but with no method
, the method will default to
POST.
If no request body is specified in the mock then any request to that endpoint using that method will match, regardless of the request body.
Specifying request headers
Request headers can be mocked by providing the headers
property with header
type and value being object properties and values respectively.
server.mock({ path: '/my-endpoint', headers: { 'X-Foo', 'bar' } });
Requests will be handled if all the headers specified in the mock match. Any other headers on the request will match, regardless of the headers.
Mocking Responses
Customise the response for a particular mock by passing an object as the second
argument to mock
to define the response shape.
The response argument is optional. If no response is provided the mock will
return a 200
status with an empty body.
Specifying response statuses
The response status can be set by providing a status
property.
server.mock({ path: '/my-endpoint' }, { status: 201 });
If no status is specified the response will default to 200
.
Specifying response bodies
The response body can be set by providing a body
property.
server.mock({ path: '/my-endpoint' }, { body: { foo: 'bar' } });
Specifying response headers
The response headers can be set by providing a headers
property which should
be an object with header type and value being object properties and values
respectively.
server.mock({ path: '/my-endpoint' }, { headers: { 'x-foo': 'bar' } });
The response will always contain the specified headers but may also contain additional headers automatically added by the server.
Recording
All mocks and requests are logged by the server which provides easy access to this information in a format similar to the way mocks are defined. This structure is intended to make adding and adjusting mocks easier.
Getting unhandled requests
To get any requests that were not handled by a mock, call
getUnhandledRequests
.
server.getUnhandledRequests();
This will return an array of requests in the order that they occurred. For example:
[
{
"request": {
"path": "/my-endpoint",
"method": "GET"
}
},
]
Getting handled requests
To get requests that were handled by mocks including what response was returned,
call getHandledRequests
.
server.getHandledRequests();
This will return an array of requests and responses in the order that they occurred. For example:
[
{
"request": {
"path": "/my-endpoint",
"method": "GET"
},
"response": {
"status": 200
}
}
]
Getting uncalled mocks
To get a list of any mocks that have not been called, call getUncalledMocks
.
server.getUncalledMocks();
This will return an array of uncalled mocks in the order that they were defined. For example:
[
{
"request": {
"path": "GET",
"method": "/endpoint"
},
"response": {
"status": 200
}
}
]
Proxying requests
In some situations it maybe useful to be able to proxy requests that aren't mocked to a real server.
server.start(9001, 'http://realserver.com');
When a request is proxied the mock server will return the response exactly as it was returned by the real server.
Both the request and the response are recorded and are available via the
getProxiedRequests
method.
server.getProxiedRequests();
Any mocks that match will be handled first and those requests won't be proxied. This allows for selective mocking of APIs.
Current Limitations
- There is no support for non-JSON request or response bodies
- The server cannot be run with any hostname other than
localhost
- The path matcher uses Express routes under the hood so theoretically any pattern that Express supports should work, but this functionality is untested
Credits
This library was inspired and influenced by: