DEV Community

Cover image for API Integration Testing Made Easy
Anudeep
Anudeep

Posted on

API Integration Testing Made Easy

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 logo pactumjs / pactum

REST API Testing Tool for all levels in a Test Pyramid

logo

PactumJS

Build Coverage Downloads Size Platform

Stars Twitter

REST API Testing Tool for all levels in a Test Pyramid


PactumJS Demo

PactumJS is a REST API Testing Tool used to automate e2e, integration, contract & component (or service level) tests.

  • ⚑ Swift
  • 🎈 Lightweight
  • πŸš€ Simple & Powerful
  • πŸ› οΈ Compelling Mock Server
  • πŸ’Ž Elegant Data Management
  • πŸ”§ Extendable & Customizable
  • πŸ“š Clear & Comprehensive Testing Style
  • πŸ”— Component, Contract & E2E testing of APIs

----------

Documentation

This readme offers an basic introduction to the library. Head over to the full documentation at https://pactumjs.github.io

Need Help

We use Github Discussions to receive feedback, discuss ideas & answer questions.

Installation

# install pactum as a dev dependency
npm install --save-dev pactum
# install a test runner to run pactum tests
# mocha / jest / cucumber
npm install --save-dev mocha
Enter fullscreen mode Exit fullscreen mode

----------

Usage

pactum can be used for all levels…

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
{
  "product": "PlayStation 5"
}
Enter fullscreen mode Exit fullscreen mode

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
{
  "id": "1f4c99e9-12df-45d4-b455-98418f4e3b1e"
}
Enter fullscreen mode Exit fullscreen mode

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 to pass data between API calls across tests.

Let's look at the test first.

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

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
{
  "order_id": "1f4c99e9-12df-45d4-b455-98418f4e3b1e",
  "card_info": {
    "number": "1111-1111-1111-1111",
    "expiry": "11/11",
    "cvv": "111"
  }
}
Enter fullscreen mode Exit fullscreen mode

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

Response
{
  "id": "a32fce50-d4e8-4d95-b16f-57fd13fbb7df"
}
Enter fullscreen mode Exit fullscreen mode

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.

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');
Enter fullscreen mode Exit fullscreen mode

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
{
  "status": "in-progress"
}
Enter fullscreen mode Exit fullscreen mode

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 which retries on failed expectations. It is similar to fluent wait in selenium.

Let's look at the test case.

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

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

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();

});
Enter fullscreen mode Exit fullscreen mode

Conclusion

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

PactumJS abstracts the challenging parts to write tests in an easy and fun way and ultimately making API Integration testing super easy.

Discussion (0)