npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@ephesoft/mock.server.sdk

v4.1.0

Published

Mock server for use in integration tests

Downloads

5

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. |