@ephesoft/mock.server.sdk
v4.1.0
Published
Mock server for use in integration tests
Downloads
5
Keywords
Readme
README
@ephesoft/mock.server.sdk
Using the mock library
Prerequisites
Add the package to your repository:
npm i @ephesoft/mock.server.sdk --save-dev
Using the mock server
Implement your test code:
describe('MyClass integration tests', function () {
const port = 8001;
const mock = new MockServerClient(port);
before(
async function () {
await mock.start();
}
);
after(
async function() {
await mock.stop();
}
);
beforeEach(
async function () {
await mock.reset();
}
);
it('should serve as an example', async function () {
// Set up the mock to respond with our test data - responses will be replayed in order.
// Multiple scenarios may be set up as long as they have distinct endpoints.
const scenarioId: string = await mock.setup({
// Specifies which HTTP method this scenario applies to.
// It will respond to ALL HTTP methods if this property is omitted.
method: HttpMethod.Get,
// Specifies the route this scenario applies to.
// If route is omitted the scenario listens to every route.
// The following wildcards are supported:
// * - single-token wildcard
// ** - multi-token wildcard - scope is unlimited
// NOTE: If you want a starts-with expression, you should end the route with ** (/my/endpoint**)
// Defaults to '**' (all routes) if omitted.
route: '/v1/my/test/endpoint?active=true', // route must be an exact match
// Set to true if you want to disable request capture for the scenario.
// Defaults to false if omitted.
disableRequestCapture: false,
// The responses to be returned - there must be at least one.
// Supported options are:
// status (required) - HTTP response status code
// statusText (optional) - HTTP response HTTP status text
// headers (optional) - HTTP response headers
// body (optional) - HTTP response body (may be string or object - object will be converted to JSON)
// delayMilliseconds (optional) - The number of milliseconds to wait before sending a response
// repeat (optional) - The number of times to repeat the response - set to true to repeat indefinitely
responses: [
{
status: 201,
headers: {
'content-type': 'application/json'
},
body: MockUploadData.startUploadWebResponse
}
]
});
const baseTestUrl = mock.serverUrl;
const testTarget = new MyClass(baseTestUrl);
// TODO: Execute test scenario and validate the outcome here
// Validate the requests made to the test server
const requests: MockRequest[] = await mock.teardown(scenarioId);
expect(requests).to.deep.equal([
{
route: '/v1/my/test/endpoint?active=true',
method: HttpMethod.Post,
headers: {
accept: 'application/json',
connection: 'close',
'content-length': '34',
'content-type': 'application/json',
host: 'localhost:8001'
},
body: 'expected request body'
}
]);
});
});
Server endpoints
POST /mock/setup
Sets up a test scenario.
Request
{
method?: HttpMethod | HttpMethod[];
route?: string;
disableRequestCapture?: boolean;
responses: MockResponse[];
}
| Property | Description |
|:----------------------|:-|
| method | The HTTP method(s) the scenario will respond to.If this property is omitted, the scenario will respond to any HTTP method. |
| route | The route (or path) the scenario will respond to (case insensitive).NOTE: Unless specified, query string parameters are not considered part of the route.A single-segment wildcard (*) may be used to allow any value in a segment:- "my/*/path" will match "my/test/path" but not "my/really/long/path"- "my/*" will match "my/", "my/?active=true", "my/path", and "my/path?active=true" but not "my?active=true"- "/my/test/path?active=*" will match "my/test/path?active=false" and "my/test/path?active=false&q=examplesearchtext" but not "my/test/path?q=examplesearchtext" or "my/test/path?q=examplesearchtext&active=false"A multiple-segment wildcard (**) may be used to match across segments- "my/path**" will match "my/path", "my/path?active=true", "my/pathwithextracharacters", "my/pathwith/extra/segments", and "my/path/test?active=true" but will not match "my/pat"- "my/path**true" will match "my/pathtrue", "my/path?active=true", "my/path/test/true", and "my/path?active=false&unique=true" but not "my/path"If this property is omitted, the scenario will respond to any route. |
| disableRequestCapture | If set to true, requests will not be captured for this scenario.Defaults to false
if omitted. |
| responses | The responses to be returned for the scenario (see definition below). |
{
status: number;
statusText?: string;
headers?: Record<string, string | string[]>;
body?: string;
delayMilliseconds?: number;
repeat?: number | boolean;
}
| Property | Description |
|:------------------|:-|
| headers | The headers to be returned with the response. |
| status | The status code to be returned (ex: 200, 400, etc.). |
| statusText | The status text to be returned (ex: "OK", "Not Found", etc.)If this property is omitted, status text will be defaulted based on the value of the status property. |
| body | The response body to be returned.If this property is omitted, the response will not have a body. |
| delayMilliseconds | The approximate number of milliseconds to wait after the request is received before returning a response.If this property is omitted, the response will be returned immediately after receiving the request. |
| repeat | The number of times to repeat the response (excluding the first time). For example, if we specify a repeat of 2, the same response will be returned three times.Set repeat to true
to repeat the response indefinitely.If this property is omitted, the response will be returned only once. |
Response
Status 200 (OK)
{
"scenarioId": "4f5cdfa1-76df-4c06-8157-55f51e880d76"
}
| Property | Description |
|:-----------|:-|
| scenarioId | The ID of the scenario that was created. This will be sent to the POST /mock/teardown
endpoint to complete the scenario. |
Status 911 (Invalid Scenario)
The scenario was found to be invalid.
{
"message": "Scenario is invalid",
"error": {
"type": "InvalidScenarioError",
"message": "Additional responses cannot be added after an indefinitely repeating response",
"stack": "InvalidScenarioError: Additional responses cannot be added after an indefinitely repeating response\n at MockEngine.assertAllResponsesReachable (C:\\Git\\test\\mock.server.sdk\\dist\\server\\mock\\MockEngine.js:154:23)\n at MockEngine.setup (C:\\Git\\test\\mock.server.sdk\\dist\\server\\mock\\MockEngine.js:75:14)\n at C:\\Git\\test\\mock.server.sdk\\dist\\server\\handlers\\setupHandler.js:8:43\n at C:\\Git\\test\\mock.server.sdk\\dist\\server\\MockServer.js:42:24\n at Layer.handle [as handle_request] (C:\\Git\\test\\mock.server.sdk\\node_modules\\express\\lib\\router\\layer.js:95:5)\n at next (C:\\Git\\test\\mock.server.sdk\\node_modules\\express\\lib\\router\\route.js:144:13)\n at next (C:\\Git\\test\\mock.server.sdk\\node_modules\\express\\lib\\router\\route.js:138:14)\n at next (C:\\Git\\test\\mock.server.sdk\\node_modules\\express\\lib\\router\\route.js:138:14)\n at next (C:\\Git\\test\\mock.server.sdk\\node_modules\\express\\lib\\router\\route.js:138:14)\n at next (C:\\Git\\test\\mock.server.sdk\\node_modules\\express\\lib\\router\\route.js:138:14)"
}
}
Status 912 (Scenario Conflict)
The scenario method and route overlap with an existing scenario (for example, a scenario listening for POST /v1/*
will conflict with a scenario listening for POST /v1/dostuff
).
{
"message": "Scenario overlaps with another scenario",
"error": {
"type": "ScenarioCollisionError",
"message": "Scenario collides with existing scenario acf2e7a2-5471-44f2-81c5-7aa866c368f7",
"stack": "ScenarioCollisionError: Scenario collides with existing scenario acf2e7a2-5471-44f2-81c5-7aa866c368f7\n at MockEngine.assertNoCollisions (C:\\Git\\test\\mock.server.sdk\\dist\\server\\mock\\MockEngine.js:124:27)\n at MockEngine.setup (C:\\Git\\test\\mock.server.sdk\\dist\\server\\mock\\MockEngine.js:73:14)\n at C:\\Git\\test\\mock.server.sdk\\dist\\server\\handlers\\setupHandler.js:8:43\n at C:\\Git\\test\\mock.server.sdk\\dist\\server\\MockServer.js:42:24\n at Layer.handle [as handle_request] (C:\\Git\\test\\mock.server.sdk\\node_modules\\express\\lib\\router\\layer.js:95:5)\n at next (C:\\Git\\test\\mock.server.sdk\\node_modules\\express\\lib\\router\\route.js:144:13)\n at next (C:\\Git\\test\\mock.server.sdk\\node_modules\\express\\lib\\router\\route.js:138:14)\n at next (C:\\Git\\test\\mock.server.sdk\\node_modules\\express\\lib\\router\\route.js:138:14)\n at next (C:\\Git\\test\\mock.server.sdk\\node_modules\\express\\lib\\router\\route.js:138:14)\n at next (C:\\Git\\test\\mock.server.sdk\\node_modules\\express\\lib\\router\\route.js:138:14)"
}
}
Status 998 (Invalid Request)
The request was missing required data or contained bad data. The response will contain the details of the issue.
{
"message": "Request is invalid",
"error": {
"type": "InvalidRequestError",
"message": "No data in request body",
"stack": "InvalidRequestError: No data in request body\n at C:\\Git\\test\\mock.server.sdk\\dist\\server\\handlers\\setupHandler.js:12:19\n at C:\\Git\\test\\mock.server.sdk\\dist\\server\\MockServer.js:42:24\n at Layer.handle [as handle_request] (C:\\Git\\test\\mock.server.sdk\\node_modules\\express\\lib\\router\\layer.js:95:5)\n at next (C:\\Git\\test\\mock.server.sdk\\node_modules\\express\\lib\\router\\route.js:144:13)\n at next (C:\\Git\\test\\mock.server.sdk\\node_modules\\express\\lib\\router\\route.js:138:14)\n at next (C:\\Git\\test\\mock.server.sdk\\node_modules\\express\\lib\\router\\route.js:138:14)\n at next (C:\\Git\\test\\mock.server.sdk\\node_modules\\express\\lib\\router\\route.js:138:14)\n at next (C:\\Git\\test\\mock.server.sdk\\node_modules\\express\\lib\\router\\route.js:138:14)\n at next (C:\\Git\\test\\mock.server.sdk\\node_modules\\express\\lib\\router\\route.js:138:14)\n at next (C:\\Git\\test\\mock.server.sdk\\node_modules\\express\\lib\\router\\route.js:138:14)"
}
}
POST /mock/teardown
Deletes the specified scenario and returns the requests that were handled by the scenario.
Request
{
"scenarioId": string;
}
| Property | Description |
|:-----------|:-|
| scenarioId | The ID of the scenario to tear down. The scenario ID is returned from POST /mock/setup
. |
Status 200 (OK)
Scenario was successfully torn down. Response body contains the requests made to the scenario endpoint(s).
Assuming we set up our scenario like this:
{
"method": "POST",
"route": "test/route",
"responses": [
{
"status": 200,
"headers": {
"content-type": "application/json"
},
"body": {"key":"value"},
"repeat": 1
}
]
}
And made the following calls:
POST: test/route?active=true
{"request": 1}
POST: test/route
{"request": 2}
Then the tear-down response will look something like this:
[
{
"route": "/test/route?active=true",
"method": "POST",
"headers": {
"content-type": "application/json",
"accept": "*/*",
"postman-token": "1b1df62d-00d8-467b-b5ea-ec68b8a9c8ef",
"host": "localhost:8010",
"accept-encoding": "gzip, deflate, br",
"connection": "keep-alive",
"content-length": "14"
},
"body": "{\"request\": 1}"
},
{
"route": "/test/route",
"method": "POST",
"headers": {
"content-type": "application/json",
"accept": "*/*",
"postman-token": "0c701aa6-2a65-4dc7-a9bc-eb2427d322c2",
"host": "localhost:8010",
"accept-encoding": "gzip, deflate, br",
"connection": "keep-alive",
"content-length": "14"
},
"body": "{\"request\": 2}"
}
]
####### Status 998 (Invalid Request)
The request was missing required data or contained bad data. The response will contain the details of the issue.
{
"message": "Request is invalid",
"error": {
"type": "InvalidRequestError",
"message": "No data in request body",
"stack": "InvalidRequestError: No data in request body\n at C:\\Git\\test\\mock.server.sdk\\dist\\server\\handlers\\teardownHandler.js:12:19\n at C:\\Git\\test\\mock.server.sdk\\dist\\server\\MockServer.js:42:24\n at Layer.handle [as handle_request] (C:\\Git\\test\\mock.server.sdk\\node_modules\\express\\lib\\router\\layer.js:95:5)\n at next (C:\\Git\\test\\mock.server.sdk\\node_modules\\express\\lib\\router\\route.js:144:13)\n at next (C:\\Git\\test\\mock.server.sdk\\node_modules\\express\\lib\\router\\route.js:140:7)\n at next (C:\\Git\\test\\mock.server.sdk\\node_modules\\express\\lib\\router\\route.js:140:7)\n at next (C:\\Git\\test\\mock.server.sdk\\node_modules\\express\\lib\\router\\route.js:140:7)\n at next (C:\\Git\\test\\mock.server.sdk\\node_modules\\express\\lib\\router\\route.js:140:7)\n at next (C:\\Git\\test\\mock.server.sdk\\node_modules\\express\\lib\\router\\route.js:140:7)\n at next (C:\\Git\\test\\mock.server.sdk\\node_modules\\express\\lib\\router\\route.js:140:7)"
}
}
Status 910 (Scenario Not Found)
The requested scenario does not exist.
{
"message": "Scenario not found",
"error": {
"type": "ScenarioNotFoundError",
"message": "No scenario was found for the ID ou812",
"stack": "ScenarioNotFoundError: No scenario was found for the ID ou812\n at MockEngine.teardown (C:\\Git\\test\\mock.server.sdk\\dist\\server\\mock\\MockEngine.js:88:19)\n at C:\\Git\\test\\mock.server.sdk\\dist\\server\\handlers\\teardownHandler.js:8:41\n at C:\\Git\\test\\mock.server.sdk\\dist\\server\\MockServer.js:42:24\n at Layer.handle [as handle_request] (C:\\Git\\test\\mock.server.sdk\\node_modules\\express\\lib\\router\\layer.js:95:5)\n at next (C:\\Git\\test\\mock.server.sdk\\node_modules\\express\\lib\\router\\route.js:144:13)\n at next (C:\\Git\\test\\mock.server.sdk\\node_modules\\express\\lib\\router\\route.js:140:7)\n at next (C:\\Git\\test\\mock.server.sdk\\node_modules\\express\\lib\\router\\route.js:140:7)\n at next (C:\\Git\\test\\mock.server.sdk\\node_modules\\express\\lib\\router\\route.js:140:7)\n at next (C:\\Git\\test\\mock.server.sdk\\node_modules\\express\\lib\\router\\route.js:140:7)\n at next (C:\\Git\\test\\mock.server.sdk\\node_modules\\express\\lib\\router\\route.js:140:7)"
}
}
POST /mock/reset
Deletes all scenarios (requests and responses), resetting the server to a freshly-started state.
Request
No request body is necessary. If you send one in, it will be ignored.
Response
Status Code 200 (OK)
Mock server was successfully reset.
The response will not contain a body.
POST /mock/stop
Releases the port the mock server is listening on. NOTE: The server won't actually stop until the node process is terminated.
Request
No request body is necessary. If you send one in, it will be ignored.
Response
Status 200 (OK)
Server is stopping. The response body will be plain text.
Stopping server...
GET /mock/state
Gets the current state of all scenarios active on the server without modifying them.
Request
No request body is necessary. If you send one in, it will be ignored.
Response
Status 200 (OK)
Assuming we set up a single scenario:
{
"method": "POST",
"route": "test/route",
"responses": [
{
"status": 201,
"headers": {
"content-type": "application/json"
},
"body": "{}"
},
{
"status": "800",
"statusText": "Special",
"headers": {
"content-type": "application/json",
"x-range": ["1", "3"]
},
"body": { "key": "value" },
"repeat": 1
},
{
"status": 204
}
]
}
And called the endpoint twice:
POST: test/route?active=true
{"request": 1}
POST: test/route
{"request": 2}
Then the state will look something like:
[
{
"id": "3afdc38a-f4cf-4b5c-8cba-cbddb1f390ee",
"methods": [
"POST"
],
"route": "test/route",
"disableRequestCapture": false,
"requests": [
{
"route": "/test/route?active=true",
"method": "POST",
"headers": {
"content-type": "application/json",
"accept": "*/*",
"postman-token": "a748bb52-3f5f-4307-92f1-5a8e4bf3de30",
"host": "localhost:8010",
"accept-encoding": "gzip, deflate, br",
"connection": "keep-alive",
"content-length": "14"
},
"body": "{\"request\": 1}"
},
{
"route": "/test/route",
"method": "POST",
"headers": {
"content-type": "application/json",
"accept": "*/*",
"postman-token": "25328a70-631a-431f-8dcd-9e529ac6e38f",
"host": "localhost:8010",
"accept-encoding": "gzip, deflate, br",
"connection": "keep-alive",
"content-length": "14"
},
"body": "{\"request\": 2}"
}
],
"responses": [
{
"status": "800",
"statusText": "Special",
"headers": {
"content-type": "application/json",
"x-range": [
"1",
"3"
]
},
"body": {
"key": "value"
},
"repeat": 0 // This is now zero because we already executed this response once
},
{
"status": 204
}
]
}
]
GET /mock/ping
Endpoint for determining whether the mock server is running.
Request
No request body is necessary. If you send one in, it will be ignored.
Response
Status 200 (OK)
Server is running. Response body will be plain text.
pong
(ANY) /echo
Echos the details of the request back in JSON format.
Request
Request body may be anything (or may be omitted).
Response
Status 200 (OK)
Request successfully echoed.
If we send a POST to "echo/test/path" with a JSON body of { "key": "value" }
, we will receive a response something like:
{
"route": "/test/path",
"method": "POST",
"headers": {
"content-type": "application/json",
"accept": "*/*",
"host": "localhost:8010",
"accept-encoding": "gzip, deflate, br",
"connection": "keep-alive",
"content-length": "18"
},
"body": "{ \"key\": \"value\" }"
}
| Property | Description | |:---------|:-------------------------------------------------------------------------------| | route | The portion of the request path following "echo/". | | method | The HTTP method used to send the request. | | headers | The headers included with the request. | | body | The request body (this property will be absent if request did not have a body). |
(ANY) **
Any call to an endpoint not listed above will be treated as a scenario execution. It will either return the next configured response or will respond with an error.
Request
Request body may be anything (or may be omitted).
Response
Status 900 (Response Not Found)
{
"message": "Unexpected call to mock service",
"error": {
"type": "ResponseNotFoundError",
"message": "No response was found for GET:/test/path",
"stack": "ResponseNotFoundError: No response was found for GET:/test/path\n at MockEngine.getScenario (C:\\Git\\test\\mock.server.sdk\\dist\\server\\mock\\MockEngine.js:174:15)\n at MockEngine.processRequest (C:\\Git\\test\\mock.server.sdk\\dist\\server\\mock\\MockEngine.js:100:31)\n at C:\\Git\\test\\mock.server.sdk\\dist\\server\\handlers\\scenarioHandler.js:12:41\n at C:\\Git\\test\\mock.server.sdk\\dist\\server\\MockServer.js:42:24\n at Layer.handle [as handle_request] (C:\\Git\\test\\mock.server.sdk\\node_modules\\express\\lib\\router\\layer.js:95:5)\n at next (C:\\Git\\test\\mock.server.sdk\\node_modules\\express\\lib\\router\\route.js:144:13)\n at next (C:\\Git\\test\\mock.server.sdk\\node_modules\\express\\lib\\router\\route.js:140:7)\n at next (C:\\Git\\test\\mock.server.sdk\\node_modules\\express\\lib\\router\\route.js:140:7)\n at next (C:\\Git\\test\\mock.server.sdk\\node_modules\\express\\lib\\router\\route.js:140:7)\n at next (C:\\Git\\test\\mock.server.sdk\\node_modules\\express\\lib\\router\\route.js:140:7)"
}
}
Error Responses
Response Status
NOTE: The mock server uses non-standard response codes to avoid its error responses being confused for scenario playback responses.
| Status Code | Description | |:------------|:------------| | 900 | A request was made for which no scenario is available OR all responses for the scenario were already used | | 910 | No scenario was found for the provided scenario ID | | 911 | The scenario was found to be invalid - response body contains the details | | 912 | An attempt was made to set up a scenario which would conflict with another scenario | | 998 | The request was invalid | | 999 | Internal error |
Response Body
Error responses will have a JSON body in the following format:
{
"message": string,
"error": {
"type": string,
"message": string,
"stack": string
}
}
| Property | Description | |:--------------|:-------------------------------| | message | A user-friendly error message. | | error.type | The error name. | | error.message | The error message. | | error.stack | The full error stack. |
Development
NPM run targets
Run NPM commands as
npm run command
Available Commands
|Command|Description| |:-|:-| | audit | Executes a dependency audit using the default NPM registry | | audit:fix | Attempts an automatic fix of any audit failures. | | build | Cleans the dist folder and transpiles/webpacks the code | | clean | Deletes the working folders like dist and docs | | clean:docs | Deletes the docs folder | | create:beta | Auto versions the package to the next available beta version. | | docs | Create API documentation for the package in a "docs" folder | | docs:bitbucket | Creates API documentation in Bitbucket markdown format in a "docs" folder | | lint | Performs static analysis on the TypeScript files. | | lint:fix | Performs static analysis on the TypeScript files and attempts to automatically fix errors. | | lint:report | Performs static analysis on the TypeScript files using the default validation rules and logs results to a JSON file. | | pack | Creates a local tarball package | | postbuild| Automatically ran during build. | | postprepare | Automatically ran during package install to setup tooling. | | prebuild | Automatically ran during build. | | start | Instructs the TypeScript compiler to compile the ts files every time a change is detected. | | test | Executes all tests and generates coverage. | | test:coverage | Generates code coverage reports from test data. | | test:integration | Runs the integration tests. | | test:unit | Runs the unit tests. | | validate:ga | Validates the package version is valid for GA release. | | validate:beta | Validates the package version is valid for a Beta release. |