API Integration Testing Made Easy
Tags: api, testing, javascript, pactumjs
Published: 2021-10-22 14:37:51

Writing integration tests for API's is challenging in a micro-services world as it involves multiple API's from multiple components.

In this article we will be focusing on the two major challenges of writing API Integration Testing. It also talks about how to overcome them by using modern testing tools and techniques. We will be using **PactumJS** to write automated API integration test cases.

{% github pactumjs/pactum %}


## Challenges

These are the two things of many that I personally felt challenging while writing API Integration Tests.

1. Passing data across tests.
2. Retry on failed expectations.

## Example

It's always better to have an example to understand the core concepts of a topic under discussion.

Let's take a simple example of an e-commerce application with the following API endpoints for processing an order.

- **POST** `/api/orders` (_for placing an order_) 
- **POST** `/api/payments` (_for making a payment_)
- **GET** `/api/payments/{payment_id}` (_for fetching the status of payment_)

### Workflow

To make things clear, the requests and responses shown below are overly simplified.

#### Step 1 - Place Order

A user comes in and makes a **POST** request to `/api/orders` with the following payload to place an order.

##### Request Payload
```json
{
  "product": "PlayStation 5"
}
```
> _At the time of writing this article, it's highly impossible to buy the above product. At-least in some places._ 🙂

Now the server responds with the following response body which contains the order `id`. 

##### Response
```json
{
  "id": "1f4c99e9-12df-45d4-b455-98418f4e3b1e"
}
```

This order `id` is dynamically generated by the API server. We need to grab it and pass it to the other endpoints like **payments** to complete the order.

Using any testing library, we can save the response in a variable and use them later. It works but not efficient. Because when we write integration tests for large scale applications, it forces us to pass significant amount of data between tests and API calls. Declaring intermediary variables will damage the readability of the code.

To overcome this challenge PactumJS comes with a concept of [Data Store](https://pactumjs.github.io/#/integration-testing?id=stores) to pass data between API calls across tests.

Let's look at the test first.

```js
await pactum.spec()
    .post('/api/orders')
    .withJson({
      "product": "PlayStation 5"
    })
    .expectStatus(200)
    .stores('OrderID', 'id');
```

The above test will make a **POST** request to `/api/orders` with given `json` payload and once the response is received it expects the status should be `200` and **stores** the value of `id` into a special variable called `OrderID` which is internal to PactumJS.

#### Step 2 - Make Payment

The next step is to make the payment. Now the user makes a **POST** request to `/api/payments` with the following payload.

##### Request Payload
```json
{
  "order_id": "1f4c99e9-12df-45d4-b455-98418f4e3b1e",
  "card_info": {
    "number": "1111-1111-1111-1111",
    "expiry": "11/11",
    "cvv": "111"
  }
}
```

Now the API responds with the following response body which contains payment `id`. 

##### Response
```json
{
  "id": "a32fce50-d4e8-4d95-b16f-57fd13fbb7df"
}
```

Now let's talk about the test case.

As you observed, the `order id` from the previous request is included in the request payload.

To get the value of special internal variable, PactumJS uses a special pattern - `$S{<variable-name>}` to access it.

Let's look at the test.

```js
await pactum.spec()
    .post('/api/payments')
    .withJson({
      "order_id": "$S{OrderID}",
      "card_info": {
        "number": "1111-1111-1111-1111",
        "expiry": "11/11",
        "cvv": "111"
      }
    })
    .expectStatus(200)
    .stores('PaymentID', 'id');
```

PactumJS will internally replace `$S{OrderID}` with `1f4c99e9-12df-45d4-b455-98418f4e3b1e` before making the request.

In the above test case we are also saving the payment `id` into the special variable `PaymentId` using the **stores** method. Using the payment `id` we can track the status of the payment. So this brings us to the final step of our integration test.

#### Step 3 - Wait for Payment to be completed.

To get the status of the payment, user makes a **GET** request to the `/api/payments/{payment_id}` endpoint.

The API responds with the following response body. 

##### Response
```json
{
  "status": "in-progress"
}
```

As you see, the status is still `in-progress`. We need to wait for few seconds for the payment to be completed.

Including hard waits is a bad practice in testing. PactumJS comes with a concept of [retry mechanism](https://pactumjs.github.io/#/integration-testing?id=retry-mechanism) which retries on failed expectations. It is similar to **fluent wait** in selenium.

Let's look at the test case.

```js
await pactum.spec()
    .get('/api/payments/{id}')
    .withPathParams('id', '$S{PaymentID}')
    .expectStatus(200)
    .expectJson({
      "status": "completed"
    })
    .retry();
```

By default it retries `3` times with a delay of `1000ms` between each retry.

## Complete Test

Now let's take a look at the entire test using [PactumJS](https://pactumjs.github.io) and [mocha](https://mochajs.org/).

```js
const pactum = require('pactum');

it('order PS5 and make payment', async () => {
  await pactum.spec()
    .post('/api/orders')
    .withJson({
      "product": "PlayStation 5"
    })
    .expectStatus(200)
    .stores('OrderID', 'id');

  await pactum.spec()
    .post('/api/payments')
    .withJson({
      "order_id": "$S{OrderID}",
      "card_info": {
        "number": "1111-1111-1111-1111",
        "expiry": "11/11",
        "cvv": "111"
      }
    })
    .expectStatus(200)
    .stores('PaymentID', 'id');

  await pactum.spec()
    .get('/api/payments/{id}')
    .withPathParams('id', '$S{PaymentID}')
    .expectStatus(200)
    .expectJson({
      "status": "completed"
    })
    .retry();

});
```

## Conclusion 

Writing readable and maintainable tests is very important to make API testing productive and enjoyable experience. 

[PactumJS](https://pactumjs.github.io) abstracts the challenging parts to write tests in an easy and fun way and ultimately making API Integration testing super easy.