-
Notifications
You must be signed in to change notification settings - Fork 54
API Testing
API Testing in general can greatly improve the efficiency of our testing strategy helping us to deliver software faster than ever. It has many aspects but generally consists of making a request & validating the response.
Note: This documentation majorly focuses on request making & response validation. Advanced features of API Testing are covered in the next sections.
To get started we need to have NodeJS (>=8) installed in our system.
# create a new folder (optional)
mkdir pactum-api-testing
cd pactum-api-testing
# initialize (optional)
npm init -y
# install pactum as a dev dependency
npm install --save-dev pactum
# install a test runner to run pactum tests
# mocha / jest / cucumber
npm install mocha -g
Create a JS file & copy the below code
// test.js
const pactum = require('pactum');
it('should be a teapot', async () => {
await pactum.spec()
.get('http://httpbin.org/status/418')
.expectStatus(418);
});
Running the test
# mocha is a test framework to execute test cases
mocha test.js
Tests in pactum are clear and comprehensive. It uses numerous descriptive methods to build your requests and expectations.
Tests can be written in two styles
- Chaining the request & expectations
- Breaking the request & expectations (BDD Style)
We can build the request & expectations by chaining the descriptive methods offered by this library.
it('should have a user with name bolt', () => {
return pactum.spec()
.get('http://localhost:9393/api/users')
.withQueryParams('name', 'bolt')
.expectStatus(200)
.expectJson({
"id": 1,
"name": "bolt",
"createdAt": "2020-08-19T14:26:44.169Z"
})
.expectJsonSchema({
type: 'object',
properties: {
id: {
type: 'number'
}
}
})
.expectResponseTime(100);
});
When you want to make your tests much more clearer, you can break your spec into multiple steps. This will come into handy when integrating pactum with cucumber. See pactum-cucumber-boilerplate for more details on pactum & cucumber integration.
Use pactum.spec()
to get an instance of the spec. With spec you can build your request & expectations in multiple steps.
Once the request is built, perform the request by calling .toss()
method and wait for the promise to resolve.
Assertions should be made after the request is performed & resolved.
Assertions should be made by either using pactum.expect
or spec.response()
.
const pactum = require('pactum');
const expect = pactum.expect;
describe('should have a user with name bolt', () => {
let spec = pactum.spec();
let response;
it('given a user is requested', () => {
spec.get('http://localhost:9393/api/users');
});
it('with name bolt', () => {
spec.withQueryParams('name', 'bolt');
});
it('should return a response', async () => {
response = await spec.toss();
});
it('should return a status 200', () => {
expect(response).to.have.status(200);
});
it('should return a valid user', async () => {
spec.response().to.have.jsonLike({ name: 'snow'});
});
it('should return a valid user schema', async () => {
expect(response).to.have.jsonSchema({ type: 'object'});
});
it('should return response within 100 milliseconds', async () => {
spec.response().to.have.responseTimeLessThan(100);
});
});
The request method indicates the method to be performed on the resource identified by the given Request-URI.
await pactum.spec().get('http://httpbin.org/status/200');
await pactum.spec().post('http://httpbin.org/status/200');
await pactum.spec().put('http://httpbin.org/status/200');
await pactum.spec().patch('http://httpbin.org/status/200');
await pactum.spec().delete('http://httpbin.org/status/200');
await pactum.spec().head('http://httpbin.org/status/200');
To pass additional parameters to the request, we can chain or use the following methods individually to build our request.
Method | Description |
---|---|
withQueryParams |
request query parameters |
withHeaders |
request headers |
withBody |
request body |
withJson |
request json object |
withGraphQLQuery |
graphQL query |
withGraphQLVariables |
graphQL variables |
withForm |
object to send as form data |
withMultiPartFormData |
object to send as multi part form data |
withRequestTimeout |
sets request timeout |
withCore |
http request options |
withAuth |
basic auth details |
__setLogLevel |
sets log level for troubleshooting |
toss (optional) |
runs the spec & returns a promise |
Use withQueryParams
to pass query parameters to the request.
it('get random male user from India', async () => {
await pactum.spec()
.get('https://randomuser.me/api')
.withQueryParams('gender', 'male')
.withQueryParams({
'country': 'IND'
})
.expectStatus(200);
});
Use withHeaders
to pass headers to the request.
it('get all comments', async () => {
await pactum.spec()
.get('https://jsonplaceholder.typicode.com/comments')
.withHeaders('Authorization', 'Basic abc')
.withHeaders({
'Content-Type': 'application/json'
})
.expectStatus(200);
});
Use withBody
or withJson
methods to pass the body to the request.
it('post body', async () => {
await pactum.spec()
.post('https://jsonplaceholder.typicode.com/posts')
.withBody('{ "title": "foo", "content": "bar"}')
.expectStatus(201);
});
it('post json object', async () => {
await pactum.spec()
.post('https://jsonplaceholder.typicode.com/posts')
.withJson({
title: 'foo',
body: 'bar',
userId: 1
})
.expectStatus(201);
});
Use withForm
or withMultiPartFormData
to pass form data to the request.
- Under the hood, pactum uses
phin.form
-
content-type
header will be auto updated toapplication/x-www-form-urlencoded
it('post with form', async () => {
await pactum.spec()
.post('https://httpbin.org/forms/posts')
.withForm({
title: 'foo',
body: 'bar',
userId: 1
})
.expectStatus(201);
});
- Under the hood it uses form-data
-
content-type
header will be auto updated tomultipart/form-data
it('post with multipart form data', async () => {
await pactum.spec()
.post('https://httpbin.org/forms/posts')
.withMultiPartFormData('file', fs.readFileSync('a.txt'), { contentType: 'application/js', filename: 'a.txt' })
.expectStatus(201);
});
We can also directly use the form-data object.
const form = new pactum.request.FormData();
form.append(/* form data */);
it('post with multipart form data', async () => {
await pactum.spec()
.post('https://httpbin.org/forms/posts')
.withMultiPartFormData(form)
.expectStatus(201);
});
Use withGraphQLQuery
or withGraphQLVariables
to pass GraphQL data to the request. Works for only POST requests.
it('post graphql query & variables', async () => {
await pactum.spec()
.post('https://jsonplaceholder.typicode.com/posts')
.withGraphQLQuery(
`
query HeroNameAndFriends($episode: Episode) {
hero(episode: $episode) {
name
friends {
name
}
}
}
`
)
.withGraphQLVariables({
"episode": "JEDI"
})
.expectStatus(201);
});
By default, pactum's request will timeout after 3000 ms. To increase the timeout for the current request use the withRequestTimeout
method. Make Sure To Increase The Test Runners Timeout As Well
it('some action that will take more time to complete', async () => {
// increase mocha timeout here
await pactum.spec()
.post('https://jsonplaceholder.typicode.com/posts')
.withJson({
title: 'foo',
body: 'bar',
userId: 1
})
.withRequestTimeout(5000)
.expectStatus(201);
});
Expectations help to assert the response received from the server.
Method | Description |
---|---|
expect |
runs custom expect handler |
expectStatus |
check HTTP status |
expectHeader |
check HTTP header key + value |
expectHeaderContains |
check HTTP header key + partial value |
expectBody |
check exact match of body |
expectBodyContains |
check body contains the value |
expectJson |
check exact match of json |
expectJsonAt |
check json using json-query |
expectJsonLike |
check loose match of json |
expectJsonLikeAt |
check json like using json-query |
expectJsonSchema |
check json schema |
expectJsonSchemaAt |
check json schema using json-query |
expectJsonMatch |
check json to match |
expectJsonMatchAt |
check json to match using json-query |
expectResponseTime |
check response time |
wait |
wait before performing validation |
Expecting Status Code & Headers from the response.
const expect = pactum.expect;
it('get post with id 1', async () => {
const response = await pactum.spec()
.get('https://jsonplaceholder.typicode.com/posts/1')
.expectStatus(200)
.expectHeader('content-type', 'application/json; charset=utf-8')
.expectHeader('connection', /\w+/)
.expectHeaderContains('content-type', 'application/json');
expect(response).to.have.status(200);
expect(response).to.have.header('connection', 'close');
});
Checks if the request is completed within a specified duration (ms).
Most REST APIs will return a JSON response. This library has few methods to validate a JSON response in many aspects.
Performs deep equal.
it('get post with id 1', async () => {
const response = await pactum.spec()
.get('https://jsonplaceholder.typicode.com/posts/1')
.expectStatus(200)
.expectJson({
"userId": 1,
"id": 1,
"title": "some title",
"body": "some body"
});
// Chai Style Assertions
// pactum.expect(response).should.have.json({});
});
Allows validation of specific part in a JSON. See json-query for more usage details.
- Performs deep equal or strict equal.
- Order of items in an array does matter.
it('get people', async () => {
const response = await pactum.spec()
.get('https://some-api/people')
.expectStatus(200)
.expectJson({
people: [
{ name: 'Matt', country: 'NZ' },
{ name: 'Pete', country: 'AU' },
{ name: 'Mike', country: 'NZ' }
]
})
.expectJsonAt('people[country=NZ].name', 'Matt')
.expectJsonAt('people[*].name', ['Matt', 'Pete', 'Mike']);
});
Performs partial deep equal.
- Allows Regular Expressions.
- Allows Assert Expressions.
- Allows Assert Handlers.
- Order of items in an array doesn't matter.
it('posts should have a item with title -"some title"', async () => {
const response = await pactum.spec()
.get('https://jsonplaceholder.typicode.com/posts')
.expectStatus(200)
.expectJsonLike([
{
"userId": /\d+/,
"title": "some title"
}
]);
// Chai Style Assertions
// pactum.expect(response).should.have.jsonLike();
// spec.response().should.have.jsonLike();
});
Assert Expressions helps to run custom JavaScript code on a JSON that performs user defined assertions.
- Expression should contain
$V
to represent current value. - Expression should be a valid JavaScript code.
- Expression should return a boolean.
Note - String containing $V will be automatically treated as a Assert Expression
it('get users', async () => {
await pactum.spec()
.get('/api/users')
.expectJsonLike('$V.length === 10'); // api should return an array with length 10
.expectJsonLike([
{
id: 'typeof $V === "string"',
name: 'jon',
age: '$V > 30' // age should be greater than 30
}
]);
});
You are also allowed to change the default value $V
to some other string based on your usage. Be cautious that all the strings containing the new value will be treated as assert expressions and pactum will try to evaluate it as a javascript code.
pactum.settings.setAssertExpressionStrategy({ includes: '$' });
it('get users', async () => {
await pactum.spec()
.get('/api/users')
.expectJsonLike([
{
name: 'jon',
age: '$ > 30' // age should be greater than 30
}
]);
});
Assert Handlers helps us to reuse the custom JavaScript assertion code on a JSON. With this we can easily extend the capabilities of expectJsonLike
to solve complex assertions.
- Handler name will be prefixed with
#
. - Handler function should return a boolean.
Note - String starting with # will be automatically treated as a Assert Handler.
Handlers is a powerful concept in pactum that helps to reuse different things. To add a assert handler use handler.addAssertHandler
function.
- First param will be the name of the assert handler which will be used in
expectJsonLike
to refer it. - Second param will be a function that accepts a context object as an argument. Context object will have
data
property that will represent the current value in JSON. It also includes optionalargs
property that includes custom arguments.
pactum.handler.addAssertHandler('number', (ctx) => {
return typeof ctx.data === 'number';
});
it('get users', async () => {
await pactum.spec()
.get('/api/users')
.expectJsonLike([
{
id: '#number',
name: 'jon'
}
]);
});
Custom arguments can be passed to the handler function by using comma separated values after :
.
pactum.handler.addAssertHandler('type', (ctx) => {
return typeof ctx.data === ctx.args[0];
});
it('get users', async () => {
await pactum.spec()
.get('/api/users')
.expectJsonLike([
{
id: '#type:number',
name: 'jon'
}
]);
});
You are also allowed to change the default value #
to some other string based on your usage. Be cautious that all the strings starting with the new value will be treated as assert handlers.
pactum.settings.setAssertHandlerStrategy({ starts: '##' });
it('get users', async () => {
await pactum.spec()
.get('/api/users')
.expectJsonLike([
{
id: '##handlerName:arg1,arg2',
name: 'jon'
}
]);
});
Allows validation of specific part in a JSON. See json-query for more usage details.
- Performs partial deep equal.
- Allows Regular Expressions.
- Allows Assert Expressions.
- Allows Assert Handlers.
- Order of items in an array doesn't matter.
it('get people', async () => {
const response = await pactum.spec()
.get('https://some-api/people')
.expectStatus(200)
.expectJson({
people: [
{ name: 'Matt', country: 'NZ' },
{ name: 'Pete', country: 'AU' },
{ name: 'Mike', country: 'NZ' }
]
})
.expectJsonAt('people[*].name', ['Matt', 'Pete', 'Mike']);
.expectJsonLikeAt('people[*].name', ['Mike', 'Matt']);
});
Allows validation of the schema of a JSON. See json-schema for more usage details.
it('get people', async () => {
const response = await pactum.spec()
.get('https://some-api/people')
.expectStatus(200)
.expectJson({
people: [
{ name: 'Matt', country: 'NZ' },
{ name: 'Pete', country: 'AU' },
{ name: 'Mike', country: 'NZ' }
]
})
.expectJsonSchema({
"type": "object",
"properties": {
"people": {
"type": "array"
}
}
});
});
Allows validation of the schema of a JSON at a specific place. See json-schema for more usage details.
it('get people', async () => {
const response = await pactum.spec()
.get('https://some-api/people')
.expectStatus(200)
.expectJson({
people: [
{ name: 'Matt', country: 'NZ' },
{ name: 'Pete', country: 'AU' },
{ name: 'Mike', country: 'NZ' }
]
})
.expectJsonSchemaAt('people', {
"type": "array"
});
});
Allows validation of JSON with a set of matchers. See Matching for more usage details.
const { like } = pactum.matchers;
it('get people', async () => {
const response = await pactum.spec()
.get('https://some-api/people')
.expectStatus(200)
.expectJsonMatch({
id: like(1),
name: 'jon'
});
});
Allows validation of specific part in a JSON with a set of matchers. See Matching for more usage details. See json-query for more usage details.
const { like } = pactum.matchers;
it('get people', async () => {
const response = await pactum.spec()
.get('https://some-api/people')
.expectStatus(200)
.expectJsonMatchAt('people[0]', {
id: like(1),
name: 'jon'
});
});
You can also add custom expect handlers to this library for making much more complicated assertions that are ideal to your requirement. You can bring your own assertion library or take advantage of popular libraries like chai.
You can simply pass a function as a parameter to expect
& then write your logic that performs assertions. A context object is passed to the handler function which contains req (request) & res (response) objects.
const chai = require('chai');
const expect = chai.expect;
const pactum = require('pactum');
const _expect = pactum.expect;
it('post should have a item with title -"some title"', async () => {
const response = await pactum.spec()
.get('https://jsonplaceholder.typicode.com/posts/5')
.expect((ctx) => {
const res = ctx.res;
_expect(res).to.have.status(200);
expect(res.json.title).equals('some title');
});
});
There might be a use case where you wanted to perform the same set of assertions. For such scenarios, you can add custom expect handlers that can be used at different places. A context object is passed to the handler function which contains req (request) & res (response) objects & data (custom data).
const chai = require('chai');
const expect = chai.expect;
const pactum = require('pactum');
const _expect = pactum.expect;
const handler = pactum.handler;
before(() => {
handler.addExpectHandler('user details', (ctx) => {
const res = ctx.res;
const user = res.json;
expect(user).deep.equals({ id: 1 });
_expect(res).to.have.status(200);
_expect(res).to.have.responseTimeLessThan(500);
_expect(res).to.have.jsonSchema({ /* some schema */ });
});
});
it('should have a post with id 5', async () => {
const response = await pactum.spec()
.get('https://jsonplaceholder.typicode.com/posts/5')
.expect('user details');
// Chai Style Assertions
// pactum.expect(response).should.have._('user details');
});
it('should have a post with id 5', async () => {
const response = await pactum.spec()
.get('https://jsonplaceholder.typicode.com/posts/6')
.expect('to have user details');
});
You are also allowed to pass custom data to common expect handlers.
before(() => {
handler.addExpectHandler('to have user details', (ctx) => {
const res = ctx.res;
const req = ctx.req;
const data = ctx.data;
/*
Add custom logic to perform based on req (request) & data (custom data passed)
*/
});
});
it('should have a post with id 5', async () => {
const response = await pactum.spec()
.get('https://jsonplaceholder.typicode.com/posts/5')
.expect('to have user details', 5); // data = 5
});
it('should have a post with id 5', async () => {
const response = await pactum.spec()
.get('https://jsonplaceholder.typicode.com/posts/6')
.expect('to have user details', { id: 6 }); // data = { id: 6 }
});
This library also offers us to set default options for all the requests that are sent through it.
Sets the base URL for all the HTTP requests.
const pactum = require('pactum');
const request = pactum.request;
before(() => {
request.setBaseUrl('http://localhost:3000');
});
it('should have a post with id 5', async () => {
// request will be sent to http://localhost:3000/api/projects
await pactum.spec()
.get('/api/projects');
});
Sets the default timeout for all the HTTP requests. The default value is 3000 ms
pactum.request.setDefaultTimeout(5000);
Sets default headers for all the HTTP requests.
pactum.request.setDefaultHeaders('Authorization', 'Basic xxxxx');
pactum.request.setDefaultHeaders({ 'content-type': 'application/json'});