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.
PactumJS
REST API Testing Tool for all levels in a Test Pyramid
PactumJS is a REST API Testing Tool used to automate e2e, integration, contract & component (or service level) tests.
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
or you can simply use
npx pactum-init
Usage
…Challenges
These are the two things of many that I personally felt challenging while writing API Integration Tests.
- Passing data across tests.
- 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"
}
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"
}
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');
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"
}
}
Now the API responds with the following response body which contains payment id
.
Response
{
"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.
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
{
"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 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();
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();
});
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.
Top comments (3)
Nice, I should try out pactum and see what other features it has
I've recently tried pactum with NestJs and loved it a lot!
Thank you for the article
Awesome! Great to hear! Thank you for taking the time to write back.