Security News
Research
Data Theft Repackaged: A Case Study in Malicious Wrapper Packages on npm
The Socket Research Team breaks down a malicious wrapper package that uses obfuscation to harvest credentials and exfiltrate sensitive data.
REST API endpoint testing tool with a mock server & compatible with pact.io for contract testing
Pactum is a REST API testing tool that allows you to write tests in a simple and readable manner. It supports various types of testing including functional, end-to-end, and performance testing. Pactum is designed to be easy to use and integrates well with other testing frameworks.
API Testing
This feature allows you to test REST APIs by making HTTP requests and validating the responses. The code sample demonstrates a GET request to a sample API and checks the status code and JSON response.
const pactum = require('pactum');
pactum.spec()
.get('https://jsonplaceholder.typicode.com/posts/1')
.expectStatus(200)
.expectJson({
userId: 1,
id: 1,
title: 'sunt aut facere repellat provident occaecati excepturi optio reprehenderit',
body: 'quia et suscipit\nsuscipit...'
})
.toss();
Mock Server
Pactum allows you to create a mock server to simulate API responses. This is useful for testing scenarios where the actual API is not available. The code sample shows how to set up a mock server that responds to a GET request.
const pactum = require('pactum');
const { mock } = pactum;
mock.addInteraction({
request: {
method: 'GET',
path: '/api/users'
},
response: {
status: 200,
body: [{ id: 1, name: 'John Doe' }]
}
});
mock.start(3000);
BDD Style Testing
Pactum supports Behavior-Driven Development (BDD) style testing, which makes tests more readable and easier to understand. The code sample demonstrates how to write BDD style tests using Given, When, and Then.
const pactum = require('pactum');
const { Given, When, Then } = require('pactum').bdd;
Given('a user exists', () => {
pactum.spec()
.get('https://jsonplaceholder.typicode.com/users/1')
.expectStatus(200);
});
When('I get the user details', () => {
pactum.spec()
.get('https://jsonplaceholder.typicode.com/users/1');
});
Then('I should see the user details', () => {
pactum.spec()
.get('https://jsonplaceholder.typicode.com/users/1')
.expectJson({
id: 1,
name: 'Leanne Graham'
});
});
Supertest is a popular library for testing Node.js HTTP servers. It provides a high-level abstraction for testing HTTP, making it easy to write tests for your APIs. Compared to Pactum, Supertest is more focused on testing Express.js applications and does not offer built-in support for BDD style testing or mock servers.
Chai-HTTP is an extension of the Chai assertion library that provides HTTP integration testing capabilities. It allows you to make HTTP requests and assertions in a fluent style. While Chai-HTTP is powerful, it requires more setup and does not offer the same level of built-in features as Pactum, such as mock servers and BDD style testing.
Nock is a library for HTTP mocking and expectations. It allows you to intercept HTTP requests and provide predefined responses, making it useful for testing without making actual network requests. Nock is more focused on mocking and does not provide the same level of API testing capabilities as Pactum.
pactum is a REST API Testing Tool that combines the implementation of consumer driven contract library Pact for Javascript.
npm install --save-dev pactum
npm install --save-dev mocha
Running a single component test expectation.
const pactum = require('pactum');
it('should be a teapot', async () => {
await pactum
.get('http://httpbin.org/status/418')
.expectStatus(418)
.toss();
});
# mocha is a test framework
mocha /path/to/test
Running a component test with the help of a mock server & a single mock interaction. If the mock interaction is not exercised, the test will fail.
const pactum = require('pactum');
before(async () => {
await pactum.mock.start();
});
it('GET - one interaction', async () => {
await pactum
.addMockInteraction({
withRequest: {
method: 'GET',
path: '/api/projects/1'
},
willRespondWith: {
status: 200,
headers: {
'content-type': 'application/json'
},
body: {
id: 1,
name: 'fake'
}
}
})
.get('http://localhost:9393/api/projects/1')
.expectStatus(200)
.expectJsonLike({
id: 1,
name: 'fake'
})
.toss();
});
after(async () => {
await pactum.mock.stop();
});
Running a component test with the help of a mock server & a single pact interaction. If the mock interaction is not exercised, the test will fail.
const pactum = require('pactum');
before(async () => {
await pactum.mock.start();
});
it('GET - one interaction', async () => {
await pactum
.addPactInteraction({
consumer: 'little-consumer',
provider: 'projects-service',
state: 'when there is a project with id 1',
uponReceiving: 'a request for project 1',
withRequest: {
method: 'GET',
path: '/api/projects/1'
},
willRespondWith: {
status: 200,
headers: {
'content-type': 'application/json'
},
body: {
id: 1,
name: 'fake'
}
}
})
.get('http://localhost:9393/api/projects/1')
.expectStatus(200)
.expectJsonLike({
id: 1,
name: 'fake'
})
.toss();
});
after(async () => {
await pactum.mock.stop();
});
Method | Description |
---|---|
get | performs a GET request on the resource |
expectStatus | expects a status code on the response |
toss | executes the test case and returns a promise |
Create a javascript file named test.js
// imports pactum library
const pactum = require('pactum');
// this is a test step in mocha
it('should be a teapot', async () => {
await pactum // pactum returns a promise
.get('http://httpbin.org/status/200') // will fetch a response from the url
.expectStatus(200) // sets an expectation on the response
.toss(); // executes the test case
});
Running the test with mocha
mocha /path/to/test.js
HTTP requests are messages sent by the client to initiate an action on the server.
Method | Description |
---|---|
get('url') | this is a HTTP method to be performed on resource |
withQuery('postId', '1') | set of parameters attached to the url |
withHeaders({}) | request headers |
withBody('Hello') | request body |
withJson({id: 1}) | request json object |
const pactum = require('pactum');
// performs a get request with query
it('GET - with query', async () => {
await pactum
.get('https://jsonplaceholder.typicode.com/comments')
.withQuery('postId', 1)
.withQuery('id', 1)
.expectStatus(200)
.toss();
});
// performs a post request with JSON
it('POST', async () => {
await pactum
.post('https://jsonplaceholder.typicode.com/posts')
.withJson({
title: 'foo',
body: 'bar',
userId: 1
})
.expectStatus(201)
.toss();
});
// performs a post request with headers & body
it('POST - with body', async () => {
await pactum
.post('https://jsonplaceholder.typicode.com/posts')
.withHeaders({
"content-type": "application/json"
})
.withBody({
title: 'foo',
body: 'bar',
userId: 1
})
.expectStatus(201)
.toss();
});
The request method indicates the method to be performed on the resource identified by the given Request-URI.
Method | Description | Usage |
---|---|---|
get | performs a GET request on the resource | await pactum.get('url').toss() |
post | performs a POST request on the resource | await pactum.post('url').toss() |
put | performs a PUT request on the resource | await pactum.put('url').toss() |
delete | performs a DELETE request on the resource | await pactum.delete('url').toss() |
patch | performs a PATCH request on the resource | await pactum.patch('url').toss() |
head | performs a HEAD request on the resource | await pactum.head('url').toss() |
// performs a delete request
it('DELETE', async () => {
await pactum
.delete('https://jsonplaceholder.typicode.com/posts/1')
.expectStatus(200)
.toss();
});
Method | Description |
---|---|
expectStatus(201) | check HTTP status |
expectHeader('key', 'value') | check HTTP header key + value (RegExp) |
expectHeaderContains('key', 'value') | check HTTP header key contains partial value (RegExp) |
expectBody('value') | check exact match of body |
expectBodyContains('value') | check body contains the value (RegExp) |
expectJson({json}) | check exact match of json |
expectJsonLike({json}) | check json contains partial value (RegExp) |
expectJsonSchema({schema}) | validate json-schema |
expectJsonQuery('path', 'value') | validate json-query |
expectResponseTime(10) | check if request completes within a specified duration (ms) |
const pactum = require('pactum');
it('GET', async () => {
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')
.expectJson({
"userId": 1,
"id": 1,
"title": "some title",
"body": "some body"
})
.expectJsonLike({
userId: 1,
id: 1
})
.expectJsonLike({
title: "some title",
body: "some body"
})
.expectJsonSchema({
"properties": {
"userId": {
"type": "number"
}
},
"required": ["userId", "id"]
})
.expectJsonSchema({
"properties": {
"title": {
"type": "string"
}
},
"required": ["title", "body"]
})
.expectResponseTime(1000)
.toss();
});
Read more about testing RESTFull services here
Component testing is defined as a software testing type, in which the testing is performed on each individual component separately without integrating with other components.
Lets assume you have an order-service which is a RESTFull API service running on port 3000. Here if the order-service has external dependencies, they are mocked using different library.
const pactum = require('pactum');
it('should fetch order details', async () => {
await pactum
// assume you have an order-service running locally on port 3000
.get('http://localhost:3000/api/orders/123')
// set an expectation of 200
.expectStatus(200)
// set an expectation on the response body
.expectJson({
orderId: '123',
price: '3030',
currency: 'INR'
})
// execute the test case
.toss();
});
Read more about component testing here
Lets assume you have an order-service which is a RESTFull API service running on port 3000. Consider if order-service depends on a currency-service to fetch the prices.
Start the order-service & overwrite the endpoint of currency-service to http://localhost:9393
const pactum = require('pactum');
before(async () => {
// starts the mock service on port 9393 (port number can be modified)
await pactum.mock.start();
});
it('should fetch order details', async () => {
await pactum
// here we are training the mock server to act as currency-service
// if the order-service fails to make a call to this endpoint then
// the test will fail with - Interaction Not Exercised
// after the execution of this spec, the mock interaction will be removed
.addMockInteraction({
withRequest: {
method: 'GET',
path: '/api/currency/INR'
},
willRespondWith: {
status: 200,
headers: {
'content-type': 'application/json'
},
body: {
id: 'INR',
description: 'Indian Rupee'
}
}
})
.get('http://localhost:3000/api/orders/123')
.expectStatus(200)
.expectJson({
orderId: '123',
price: '3030',
currency: 'INR'
})
.toss();
});
after(async () => {
// stops the mock service on port 9393 (port number can be modified)
await pactum.mock.stop();
});
If the order-service interacts with multiple services, we can add multiple mock interactions.
const pactum = require('pactum');
before(async () => {
await pactum.mock.start();
});
it('should fetch order details', async () => {
await pactum
// if the order-service fails to make a call to this endpoint then
// the test will fail with - Interaction Not Exercised
.addMockInteraction({
withRequest: {
method: 'GET',
path: '/api/currency/INR'
},
willRespondWith: {
status: 200,
headers: {
'content-type': 'application/json'
},
body: {
id: 'INR',
description: 'Indian Rupee'
}
}
})
// if the order-service fails to make a call to this endpoint then
// the test will fail with - Interaction Not Exercised
.addMockInteraction({
withRequest: {
method: 'GET',
path: '/api/allocations/123'
},
willRespondWith: {
status: 200,
headers: {
'content-type': 'application/json'
},
body: [12, 13]
}
})
.get('http://localhost:3000/api/orders/123')
.expectStatus(200)
.expectJson({
orderId: '123',
price: '3030',
currency: 'INR'
})
.toss();
});
after(async () => {
await pactum.mock.stop();
});
The scope of each interaction added through pactum.addMockInteraction()
will be restricted to current spec (it
block)
To add mock interactions that will be consumed in all the specs use - pactum.mock.addDefaultMockInteraction()
const pactum = require('pactum');
before(async () => {
await pactum.mock.start();
// adds a default mock interaction
pactum.mock.addDefaultMockInteraction({
withRequest: {
method: 'GET',
path: '/api/currency/INR'
},
willRespondWith: {
status: 200,
headers: {
'content-type': 'application/json'
},
body: {
id: 'INR',
description: 'Indian Rupee'
}
}
})
});
it('should fetch order details for id 123', async () => {
await pactum
.get('http://localhost:3000/api/orders/123')
.expectStatus(200)
.expectJson({
orderId: '123',
price: '3030',
currency: 'INR'
})
.toss();
});
it('should fetch order details for id 124', async () => {
await pactum
.get('http://localhost:3000/api/orders/124')
.expectStatus(200)
.expectJson({
orderId: '124',
price: '5643',
currency: 'INR'
})
.toss();
});
after(async () => {
// removes all default interactions
pactum.mock.removeDefaultInteractions();
await pactum.mock.stop();
});
Methods to add a mock interaction:
pactum.addMockInteraction()
pactum.mock.addDefaultMockInteraction()
Property | Type | Required | Description |
---|---|---|---|
id | string | optional | id of the interaction |
consumer | string | optional | name of the consumer |
provider | string | optional | name of the provider |
state | string | optional | state of the provider |
uponReceiving | string | optional | description of the request |
withRequest | object | required | request details |
withRequest.method | string | required | HTTP method |
withRequest.path | string | required | api path |
withRequest.headers | object | optional | request headers |
withRequest.query | object | optional | query parameters |
withRequest.body | any | optional | request body |
withRequest.ignoreQuery | boolean | optional | ignore request query |
withRequest.ignoreBody | boolean | optional | ignore request body |
willRespondWith | object | required | response details |
willRespondWith.status | number | required | response status code |
willRespondWith.headers | object | optional | response headers |
willRespondWith.body | any | required | response body |
Contract testing is a technique for testing an integration point by checking each application in isolation to ensure the messages it sends or receives conform to a shared understanding that is documented in a contract.
In a single spec we can use multiple mock interactions & multiple pact interactions.
const pactum = require('pactum');
before(async () => {
await pactum.mock.start();
});
it('should fetch order details', async () => {
await pactum
// here we are training the mock server to act as currency-service
// if the order-service fails to make a call to this endpoint then
// the test will fail with - Interaction Not Exercised
// after the execution of this spec, the pact interaction will be removed
.addPactInteraction({
consumer: 'order-service',
provider: 'currency-service',
state: 'there is INR currency',
uponReceiving: 'a request for INR currency',
withRequest: {
method: 'GET',
path: '/api/currency/INR'
},
willRespondWith: {
status: 200,
headers: {
'content-type': 'application/json'
},
body: {
id: 'INR',
description: 'Indian Rupee'
}
}
})
.get('http://localhost:3000/api/orders/123')
.expectStatus(200)
.expectJson({
orderId: '123',
price: '3030',
currency: 'INR'
})
.toss();
});
after(async () => {
await pactum.mock.stop();
});
Methods to add a pact interaction:
pactum.addPactInteraction()
pactum.mock.addDefaultPactInteraction()
Property | Type | Required | Description |
---|---|---|---|
id | string | optional | id of the interaction |
consumer | string | required | name of the consumer |
provider | string | required | name of the provider |
state | string | required | state of the provider |
uponReceiving | string | required | description of the request |
withRequest | object | required | request details |
withRequest.method | string | required | HTTP method |
withRequest.path | string | required | api path |
withRequest.headers | object | optional | request headers |
withRequest.query | object | optional | query parameters |
withRequest.body | any | optional | request body |
willRespondWith | object | required | response details |
willRespondWith.status | number | required | response status code |
willRespondWith.headers | object | optional | response headers |
willRespondWith.body | any | required | response body |
Pactum can also be used as a mock server
// The below code will run the pactum server on port 3000
const pactum = require('pactum');
pactum.mock.setDefaultPort(3000);
pactum.mock.start();
Use addDefaultMockInteraction()
and addDefaultPactInteraction()
to add default interactions to the mock server.
To add multiple use addDefaultMockInteractions()
and addDefaultPactInteractions()
.
// The below code will run the pactum server on port 3000
// with a mock interaction & a pact interaction
const pactum = require('pactum');
pactum.mock.setDefaultPort(3000);
pactum.mock.addDefaultMockInteraction({
withRequest: {
method: 'GET',
path: '/api/projects/1'
},
willRespondWith: {
status: 200,
headers: {
'content-type': 'application/json'
},
body: {
id: 1,
name: 'fake'
}
}
});
pactum.mock.addDefaultPactInteraction({
consumer: 'consumer-service',
provider: 'project-service',
state: 'there is a project with id 1',
uponReceiving: 'a request for project 1',
withRequest: {
method: 'GET',
path: '/api/projects/1'
},
willRespondWith: {
status: 200,
headers: {
'content-type': 'application/json'
},
body: {
id: 1,
name: 'fake'
}
}
});
pactum.mock.start();
FAQs
REST API Testing Tool for all levels in a Test Pyramid
The npm package pactum receives a total of 139,086 weekly downloads. As such, pactum popularity was classified as popular.
We found that pactum demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Security News
Research
The Socket Research Team breaks down a malicious wrapper package that uses obfuscation to harvest credentials and exfiltrate sensitive data.
Research
Security News
Attackers used a malicious npm package typosquatting a popular ESLint plugin to steal sensitive data, execute commands, and exploit developer systems.
Security News
The Ultralytics' PyPI Package was compromised four times in one weekend through GitHub Actions cache poisoning and failure to rotate previously compromised API tokens.