-
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.
- It can be performed at different levels of software development life cycle.
- It can be performed independent of the language used to develop the application. (java based applications API can be tested in other programming languages)
- In market there are numerous tools available that allows us to test our APIs for different test types.
Instead of using different tools for each test type, pactum comes with all the popular features in a single bundle.
So the question is Why pactum? & What makes pactum fun & easy?
- Extremely lightweight.
- Quick & easy to send requests & validate responses.
- Not tied with any of the test runners. You are allowed to choose the test runner like mocha, cucumber, jest or any other that supports promises.
- Ideal for component testing, contract testing & e2e testing.
- Ability to control the behavior of external services with a powerful mock server. (once you are familiar with api-testing using pactum make sure to read about component testing using pactum)
Lets get started with API Testing.
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
touch http.test.js
Copy the below code
const pactum = require('pactum');
it('should be a teapot', async () => {
await pactum
.get('http://httpbin.org/status/418')
.expectStatus(418);
});
Running the test
# mocha is a test framework to execute test cases
mocha http.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
.get('http://localhost:9393/api/users')
.withQueryParam('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.
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 maid after the request is performed & resolved.
Assertions can be maid either by 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.withQueryParam('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.get('http://httpbin.org/status/200');
await pactum.post('http://httpbin.org/status/200');
await pactum.put('http://httpbin.org/status/200');
await pactum.patch('http://httpbin.org/status/200');
await pactum.del('http://httpbin.org/status/200');
await pactum.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 |
---|---|
withQueryParam |
single query parameter |
withQueryParams |
multiple query parameters |
withHeader |
single request headers |
withHeaders |
multiple 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 |
__setLogLevel |
sets log level for troubleshooting |
Use withQueryParam
or withQueryParams
methods to pass query parameters to the request. We are allowed to call the query-param
methods multiple times fo the same request.
it('get random male user from India', async () => {
await pactum
.get('https://randomuser.me/api')
.withQueryParam('gender', 'male')
.withQueryParams({
'country': 'IND'
})
.expectStatus(200);
});
Use withHeader
or withHeaders
methods to pass headers to the request. We are allowed to call the header
methods multiple times fo the same request.
it('get all comments', async () => {
await pactum
.get('https://jsonplaceholder.typicode.com/comments')
.withHeader('Authorization', 'Basic abc')
.withHeader('Accept', '*')
.withHeaders({
'Content-Type': 'application/json'
})
.expectStatus(200);
});
Use withBody
or withJson
methods to pass body to the request.
it('post body', async () => {
await pactum
.post('https://jsonplaceholder.typicode.com/posts')
.withBody('{ "title": "foo", "content": "bar"}')
.expectStatus(201);
});
it('post json object', async () => {
await pactum
.post('https://jsonplaceholder.typicode.com/posts')
.withJson({
title: 'foo',
body: 'bar',
userId: 1
})
.expectStatus(201)
.toss();
});
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
.post('https://httpbin.org/forms/posts')
.withForm({
title: 'foo',
body: 'bar',
userId: 1
})
.expectStatus(201)
.toss();
});
- 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
.post('https://httpbin.org/forms/posts')
.withMultiPartFormData('file', fs.readFileSync('a.txt'), { contentType: 'application/js', filename: 'a.txt' })
.expectStatus(201)
.toss();
});
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
.post('https://httpbin.org/forms/posts')
.withMultiPartFormData(form)
.expectStatus(201)
.toss();
});
Use withGraphQLQuery
or withGraphQLVariables
to pass form graphql data to the request. Works for only POST requests.
it('post graphql query & variables', async () => {
await pactum
.post('https://jsonplaceholder.typicode.com/posts')
.withGraphQLQuery(
`
query HeroNameAndFriends($episode: Episode) {
hero(episode: $episode) {
name
friends {
name
}
}
}
`
)
.withGraphQLVariables({
"episode": "JEDI"
})
.expectStatus(201)
.toss();
});
By default pactum request will timeout after 3000 ms. To increase the timeout for the current request use 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
.post('https://jsonplaceholder.typicode.com/posts')
.withJson({
title: 'foo',
body: 'bar',
userId: 1
})
.withRequestTimeout(5000)
.expectStatus(201)
.toss();
});
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 |
expectJsonLike |
check loose match of json |
expectJsonSchema |
check json schema |
expectJsonQuery |
check json using json-query |
expectJsonQueryLike |
check json like using json-query |
expectResponseTime |
check response time |
Expecting Status Code & Headers from the response.
const expect = pactum.expect;
it('get post with id 1', async () => {
const response = await pactum
.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
.get('https://jsonplaceholder.typicode.com/posts/1')
.expectStatus(200)
.expectJson({
"userId": 1,
"id": 1,
"title": "some title",
"body": "some body"
});
});
Performs partial deep equal.
- Allows Regular Expressions.
- Order of items in an array doesn't matter.
it('posts should have a item with title -"some title"', async () => {
const response = await pactum
.get('https://jsonplaceholder.typicode.com/posts')
.expectStatus(200)
.expectJsonLike([
{
"userId": /\d+/,
"title": "some title"
}
]);
});
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 matters.
it('get people', async () => {
const response = await pactum
.get('https://some-api/people')
.expectStatus(200)
.expectJson({
people: [
{ name: 'Matt', country: 'NZ' },
{ name: 'Pete', country: 'AU' },
{ name: 'Mike', country: 'NZ' }
]
})
.expectJsonQuery('people[country=NZ].name', 'Matt')
.expectJsonQuery('people[*].name', ['Matt', 'Pete', 'Mike']);
});
Allows validation of specific part in a JSON. See json-query for more usage details.
- Performs partial deep equal.
- Order of items in an array doesn't matter.
it('get people', async () => {
const response = await pactum
.get('https://some-api/people')
.expectStatus(200)
.expectJson({
people: [
{ name: 'Matt', country: 'NZ' },
{ name: 'Pete', country: 'AU' },
{ name: 'Mike', country: 'NZ' }
]
})
.expectJsonQuery('people[*].name', ['Matt', 'Pete', 'Mike']);
.expectJsonQueryLike('people[*].name', ['Mike', 'Matt']);
});
Allows validation of schema of a JSON. See json-schema for more usage details.
it('get people', async () => {
const response = await pactum
.get('https://some-api/people')
.expectStatus(200)
.expectJson({
people: [
{ name: 'Matt', country: 'NZ' },
{ name: 'Pete', country: 'AU' },
{ name: 'Mike', country: 'NZ' }
]
})
.expectJsonSchema({
properties: {
"people": "array"
}
});
});
TODO - Request Settings
TODO - Returns
TODO - Retry
TODO - Data Management
TODO - State Handlers