Writing JavaScript API Tests with Pactum


I keep telling myself (as do some of the training sessions) that somehow I don’t get along with JavaScript. Maybe it’s me, maybe it’s him, but for some reason I can’t quite figure it out. And the fact that in my university years I was engaged in more strongly typed languages ​​​​(mainly Java), apparently, does not help me.

Nobody denies that JavaScript is a popular language and that there is a great need for JavaScript automation skills. Therefore, I decided to devote some time to learning the language and the tools available at the moment.

Since many of the tech blog posts I’ve written are about some kind of API testing (including API mocking and contract tests), I haven’t been particularly impressed with the API testing libraries available for JavaScript so far. I thought it would be a good idea to take this as a starting point.

I am a big fan of API libraries, for example − REST Assured for Java and requests for Python because they both make it easier to write API tests.

I didn’t know of any JavaScript library that was easy to use until I saw someone’s article about Pactum on LinkedIn (unfortunately, I couldn’t find it to attach a link). Pactum interested me and I decided to give it a try.

It certainly helped that I took the liberty of giving a presentation on the JavaScript Testing API to a group of testing students. There is nothing better than having to speak in public rather than scrutinizing a new topic.

Judging by the documentation, Pactum is capable of many things. Integrated testing, API mocking, contract testing… it’s all there. In this article, I focus on those features of the library that help with API testing (integration testing). Perhaps later I will explore other features of Pactum and make a whole series of articles on this topic.

The examples in this article are written using jest as a testing framework.

Let’s start with the very basics of API testing: making a GET request to an endpoint and checking the HTTP response codes for the request. In Pactum this can be done like this:

describe('Retrieving data for user with ID 1', () => {

    test('should yield HTTP status code 200', async () => {

        await pactum.spec()
            .get('http://jsonplaceholder.typicode.com/users/1')
            .expectStatus(200)
    });
});

pactum.spec() expands all the methods offered by Pactum for constructing a query. Since we don’t need to specify anything in terms of headers, cookies, or request body, we can call the GET method directly with the method get() for the endpoint of our choice, and then indicate our expectations for the response. In this case, we expect the response code to be 200, and we can check this with the method expectStatus().

Running a test (using npm run testwhich in turn calls Jest) shows that our tests passed:

Next, let’s see if we can check the value in the response header, in this case Content-Type:

test('should yield Content-Type header containing value "application/json"', async () => {

    await pactum.spec()
        .get('http://jsonplaceholder.typicode.com/users/1')
        .expectHeaderContains('content-type', 'application/json')
});

Method expectHeaderContains() looks for the response header and checks that its value contains a predefined expected value, in this case application/json. Pay attention to one point – for some reason, the header name must be specified in lowercase letters. I originally used “Content-Type” but this caused the test to fail because I couldn’t find a title by that name.

If you want a method that does an exact match, use expectHeader().

Now let’s take a look at the body of the response. Pactum has great support for JSON in the server response body, for other formats (plain text, XML,…) support seems to be limited to string-based comparison, which means you have to do a bit more of your own work. The API returns data in JSON format during tests, so this is not a problem.

Suppose we want to check that the element in the JSON response is name at the first nesting level contains a value equal to “Leanne Graham”. Method usage expectJsonMatch() in Pactum simplifies the task:

test('should yield "name" JSON body element with value "Leanne Graham"', async () => {

    await pactum.spec()
        .get('http://jsonplaceholder.typicode.com/users/1')
        .expectJsonMatch('name', 'Leanne Graham')
});

First method argument expectJsonMatch() is actually an expression json-queryso it can also be used to retrieve nested objects, like so:

test('should yield "Gwenborough" as the city within the address', async () => {

    await pactum.spec()
        .get('http://jsonplaceholder.typicode.com/users/1')
        .expectJsonMatch('address.city', 'Gwenborough')
});

What about passing data to the endpoint using the POST method instead of receiving and validating data from the endpoint? It turns out that this can be easily done with Pactum too:

describe('Posting a new post item', () => {

    test('should yield HTTP status code 201', async () => {

        let new_post = {
            "title": "My awesome new post title",
            "body": "My awesome new post body",
            "userId": 1
        }

        await pactum.spec()
            .post('http://jsonplaceholder.typicode.com/posts')
            .withJson(new_post)
            .expectStatus(201)
    });
});

Creating a JSON payload is as easy as creating it and adding it to the request using the withJson().

As a final example, I often see how easy it is to create data-driven tests using the API library. Because an API often exposes business logic, and you often need multiple combinations of inputs and corresponding expected outputs to test that logic, data-driven tests are also quite common when writing API tests.

Now, Jest does most of the hard work for us (like JUnit in Java or pytest in Python), as it provides a mechanism for data-driven tests with test.each():

describe('Retrieving user data for users', () => {

    test.each(
        [[1,'Leanne Graham'], [2,'Ervin Howell'], [3,'Clementine Bauch']]
    )('User with ID %i has name %s', async (userId, expectedName) => {

        await pactum.spec()
            .get('http://jsonplaceholder.typicode.com/users/{user}')
            .withPathParams('user', userId)
            .expectJsonMatch('name', expectedName)
    });

});

All we need to add while writing our Pactum tests is to specify the path parameter with the method withPathParams() and use it to populate the placeholder {user} in the endpoint. This mechanism is really similar to what I’m used to working with Java, C# and Python, which makes Pactum (and Jest, and even JavaScript in general) really appreciated.

Running this test gives us the following output:

Running a test in Pactum and seeing that it passes
Running a test in Pactum and seeing that it passes

In this article, you have learned just a small part of what Pactum allows you to implement. Judging by documentation, you can do a lot more with the library, and I can’t wait to learn more about Pactum in the future, especially mocking and contract testing features. I never thought I’d ever say this about a JavaScript library…

All code and examples given in the article can be found on github.


We invite everyone to the open lesson “Typical Automator Interview”, where we will talk about soft skills and hard skills for a successful interview for a Java Script test automator. Registration available link.

Similar Posts

Leave a Reply

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