API mocking in JavaScript with Pactum


I recently wrote an article about Pactum, a JavaScript library for API testing, using stubs and contract testing. In that article, I focused on the API testing capabilities that Pactum provides. In this article, I’d like to continue exploring Pactum by looking at its API mocking features in more detail.

I already have experience with API mocking libraries, first of all wire mock and wiremock.net. In this article, I’ll try to put Pactum in comparison by looking at some of the important features I’d expect from any API or library mimic tool.

Getting Started: Setting Up and Testing Your First Mock

Let’s start with the simplest example. Like wire mock and wiremock.net, Pactum offers a fake server where you can add fake server responses. Like me, users of the aforementioned tools shouldn’t have much trouble getting started with Pactum.

Running a fake server on Pactum is quite simple:

const { mock, settings } = require('pactum');

beforeEach(async () => {

    settings.setLogLevel('ERROR');

    await mock.start(9876);
});

I used the construct jest beforeEach to start a fake server before each test. Then I set the logging level to ERRORto get rid of some start and end entries that Pactum writes to the console by default since I don’t need it. Finally, I start the fake server on port 9876. That’s it.

Shutting down the fake server after each test is just as easy:

afterEach(async () => {

    await mock.stop()
});

If you want to start/stop the server only once, you can replace before starting the test beforeEach and afterEach on the beforeAll and afterAll respectively. I’ll leave it up to you. Starting and stopping the server is very, very fast, so I didn’t notice any performance degradation doing it this way.

Now that we can start and stop the server, let’s add the first mock response to it:

function addHelloWorldResponse() {

    mock.addInteraction({
        request: {
            method: 'GET',
            path: '/api/hello-world'
        },
        response: {
            status: 200,
            body: 'Hello, world!'
        }
    });
}

Mock responses are added to the fake Pactum server through so-called interactions. The interaction contains information about the request to be answered using request matching (more on this later), as well as the mock response to be returned. In this case, we want to respond to an HTTP GET request to /api/hello-world response with HTTP status code 200 and text response body Hello, world!.

To check if it works, we write a test with Pactum that makes a request to a test endpoint on our localhost on port 9876:

const pactum = require('pactum');

describe('Demonstrating that Pactum API mocking can', () => {

    test('return a basic REST response', async () => {

        addHelloWorldResponse();

        await pactum.spec()
            .get('http://localhost:9876/api/hello-world')
            .expectStatus(200)
            .expectBody('Hello, world!')
    });
});

Running this test produces the following output, from which we can see that the mock behaves as we would expect:

Running a test and seeing that the mock behaves as expected
Running a test and seeing that the mock behaves as expected

Query Matching

In the previous example, request matching (that is, looking at specific characteristics of an incoming request to determine the appropriate response) was done by looking at an HTTP operation (GET) and an endpoint (/api/hello-world). Pactum also offers a couple of other request matching strategies, including request headers and their values ​​(which is useful for authentication), request parameters and their values, and request body content.

Here is an example of how to add responses to requests with a specific value of the request parameter:

function addQueryParameterRequestMatchingResponses() {

    mock.addInteraction({
        request: {
            method: 'GET',
            path: '/api/zip',
            queryParams: {
                zipcode: 90210
            }
        },
        response: {
            status: 200,
            body: {
                zipcode: 90210,
                city: 'Beverly Hills'
            }
        }
    });

    mock.addInteraction({
        request: {
            method: 'GET',
            path: '/api/zip',
            queryParams: {
                zipcode: 12345
            }
        },
        response: {
            status: 200,
            body: {
                zipcode: 12345,
                city: 'Schenectady'
            }
        }
    });
}

This will instruct the fake Pactum server to respond to:

  • HTTP GET request to /api/zip?zipcode=90210 with response body {zipcode: 90210, city: 'Beverly Hills'}

  • HTTP GET request to /api/zip?zipcode=12345 with response body {zipcode: 12345, city: 'Schenectady'}

  • all other requests (including requests to /api/zip with other request parameter values zipcode) with an HTTP 404 response code (the default response for a non-matching request).

The GitHub repository contains tests showing that the above mock behaves as expected.

Simulate environments with varying performance

Another useful feature of any API mocking library is the ability to determine performance, or how long a fake server should wait before responding to a request. For example, the code below defines a mock that returns a response after waiting for a fixed delay of 1000 milliseconds:

function addDelayedResponse() {

    mock.addInteraction({
        request: {
            method: 'GET',
            path: '/api/delay'
        },
        response: {
            status: 200,
            fixedDelay: 1000
        }
    })
}

To be closer to reality, Pactum also allows you to randomize the delay and specify the minimum and maximum delay values.

This test, which invokes a mock response with a delay, fails with the error:

test('return a REST response with a delay', async () => {

    addDelayedResponse();

    await pactum.spec()
        .get('http://localhost:9876/api/delay')
        .expectStatus(200)
        .expectResponseTime(1000)
});

Pactum only allows you to set an upper limit on the expected response time. Since the actual response time is over 1000 milliseconds (we added a delay plus some processing time), the test fails, demonstrating that the delay was applied successfully. If it weren’t, the test would pass, because sending a request and processing a response usually only takes a couple of milliseconds.

Reusing values ​​from a query

Often, when creating a mock API response, you need to reuse values ​​from a request (unique identifiers, cookies, other dynamic values). With Pactum you can do this too:

const { like } = require('pactum-matchers');

function addReusePathParameterValueResponse() {

    mock.addInteraction({
        request: {
            method: 'GET',
            path: '/api/user/{id}',
            pathParams: {
                id: like('random-id')
            }
        },
        stores: {
            userId: 'req.pathParams.id'
        },
        response: {
            status: 200,
            body: {
                message: `Returning data for user $S{userId}`
            }
        }
    });
}

This mock definition identifies a specific part of the path as a path parameter idreferring here to the user ID, and saves it for reuse under the name userId. It can then be reused when constructing the response, which is what we do here by using it in a template string, referring to the previously stored value with $S{userId}. Note the S, which I assume refers to something like “storage” where Pactum stores values.

This data-driven test (see previous article) shows that the dummy Pactum server successfully extracts the value of the path parameter from the request and reuses it in the response body:

test.each(
[[1], [2], [3]]
)('use response templating to return the expected message for user %i', async (userId) => {

    addReusePathParameterValueResponse();

    await pactum.spec()
        .get('http://localhost:9876/api/user/{user}')
        .withPathParams('user', userId)
        .expectStatus(200)
        .expectJsonMatch('message', `Returning data for user ${userId}`)
});

Similarly, Pactum can extract query parameter values, header values, as well as request body values ​​to be reused in a response.

To summarize, I found that Pactum offers easy-to-use API mocking capabilities. But perhaps it helps me in this sense that I have some experience with WireMock.

In this article, I did not explore the possibility of simulating stateful behavior, that is, modeling “state” or “memory” in a mock API. Where WireMock does it with finite state machineswith Pactum you can probably do something similar using the construct onCall.

This is not the same as the FSM approach in WireMock (and WireMock.Net), but for simple scenarios you should be able to get similar results.

All code from this article can be found on GitHub. In the next article, I’ll explore Pactum’s contract testing capabilities.


When a tester finds a bug, he creates a task in which he describes the problem in detail. When the autotest finds an error, then … By the way, what happens when the autotest finds an error? If you do not have an answer to this question, then come to the open lesson “Report for Autotests”. We will explain everything in detail and show with examples. Registration for everyone link.

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *