Introduction
The definition of a high-quality REST API application is an application that can operate well and perform well. The performance of the REST API can be measured by performance testing to ensure the application is operational, scalable, and reliable on many different workloads. From normal workloads to unexpected heavy workloads.
Why Performance Testing?
The performance testing provides some benefits during REST API development:
Ensuring the performance of the REST API meets the desired standards, especially in unexpected workloads.
Detecting performance bottlenecks earlier before it went to production.
What is k6?
Many tools can be used for performance testing. One of those is k6. k6 is a performance testing tool developed by Grafana Labs for conducting performance testing in various platforms like REST API and web applications. The k6 is a code-based tool which means the testing script is written in a Javascript code. The k6 can be utilized even more by using additional plugins and extensions.
k6 Setup
These are the required steps for using k6:
Install the k6. The k6 is available in Docker container and binary. Make sure to install it based on the operating system that is being used.
Configure the code editor. The code editor needs to be configured to enable the IntelliSense feature to enhance the developer experience when writing testing scripts.
Writing the First Test
The k6 testing scripts can be organized into one project because when working in a project, the scripts can be organized easily.
Create the npm project by running this command.
npm init --yes
If you want to specify the additional information of the project, use
npm init
.
After that, install the k6 types for enabling IntelliSense feature.
npm install --save-dev @types/k6
The test script can be generated automatically using k6 new
command. This is the structure of the command.
k6 new <directory_name>/filename.js
make sure the
directory
is exists.
If the directory location is not specified. This command creates a test script in the current working directory. The test script can be created manually as well.
This is the example of creating a new test script in the src
directory.
k6 new src/test.js
This is the content of the created test script. The comments inside the test script is already removed.
import http from "k6/http";
import { sleep } from "k6";
export const options = {
// A number specifying the number of VUs to run concurrently.
vus: 10,
// A string specifying the total duration of the test run.
duration: "30s",
};
export default function () {
http.get("https://test.k6.io");
sleep(1);
}
The test can be executed using k6 run
command followed by the file name.
k6 run src/test.js
This is the output of the test.
After the test is executed, the k6 generates a summary output that describes information like the number of successful requests, duration of the requests, and others. This is a detailed explanation of the output summary.
Standard built-in metrics
Metric name | Description |
---|---|
checks | The rate of successful checks |
data_received | The amount of received data |
data_send | The amount of sent data |
iteration_duration | The amount of time to complete a single iteration |
iterations | The aggregate number of times the VUs execute the test script (the default function) |
vus | The current number of active virtual users |
vus_max | The maximum potential number of virtual users |
HTTP-specific built-in metrics
Metric name | Description |
---|---|
http_req_blocked | Time to spent waiting for a free TCP connection slot before initiating the request |
http_req_connecting | The duration of establishing a TCP connection to the remote host |
http_req_duration | The total duration of the request |
http_req_failed | The rate of failed requests according to setResponseCallback
|
http_req_receiving | The duration of receiving response data from the remote host |
http_req_sending | The duration of sending data to the remote host |
http_req_tls_handshaking | The duration of establishing TLS handshake session with remote host |
http_req_waiting | The duration of waiting for the response from the remote host |
http_reqs | The total amount of generated HTTP requests by k6 |
Learn more about the k6 metrics here.
Load Testing Types
There are many types of load testing for measuring the performance of the system. Each type serves different purposes and requirements.
Smoke Testing
The smoke testing ensures the system is operational with a minimal load and verifies the test scripts. The smoke testing must be executed with a small number of virtual users (from 2 up to 5 VUs) and short duration.
This is an example of smoke testing for getting all posts feature.
import http from "k6/http";
import { check } from "k6";
export const options = {
vus: 3, // 3 virtual users
duration: "40s", // duration is 40 seconds
};
export default function () {
// sends GET request
const response = http.get("https://jsonplaceholder.typicode.com/posts");
// validate the response
check(response, {
"is status 200": (r) => r.status === 200,
"is not null": (r) => r.json() !== null,
});
}
Average Load Testing
The average load testing ensures the system is operational with typical loads. Typical loads mean the workload during the regular day in a production environment.
This is the example of average load testing for getting all posts feature.
import http from "k6/http";
import { check } from "k6";
export const options = {
stages: [
{ duration: "5m", target: 100 }, // traffic ramp-up from 1 to 100 users over 5 minutes.
{ duration: "30m", target: 100 }, // stay at 100 users for 30 minutes
{ duration: "5m", target: 0 }, // ramp-down to 0 users
],
};
// function for generating random string
const generateRandomString = (start, end) => {
const res = Math.random().toString(36).substring(start, end);
return res;
};
// function for generating random ID
const generateRandomId = () => {
const id = Math.floor(Math.random() * 1000);
return id;
};
export default function () {
// prepare request body
const requestBody = {
title: generateRandomString(1, 7),
body: generateRandomString(1, 20),
userId: generateRandomId(),
};
// sends POST request
const response = http.post(
"https://jsonplaceholder.typicode.com/posts",
JSON.stringify(requestBody),
{
headers: {
"Content-Type": "application/json",
Accept: "application/json",
},
}
);
// validate the response
check(response, {
"is status 201": (r) => r.status === 201,
"is not null": (r) => r.json() !== null,
"is contains valid id": (r) => r.json().id > 0,
"is contains valid title": (r) => r.json().title !== "",
"is contains valid body": (r) => r.json().body !== "",
});
}
Stress Testing
The stress testing ensures the system is operational with heavier loads than usual. The stress testing verifies the stability and reliability of the system under heavy workloads. The system may receive heavy workloads in many moments such as flash-sale, payday, school or university admission process, and other similar moments. When conducting the stress testing, the number of workloads must be heavier than typical workloads and must be executed after average load testing.
This is an example of stress testing for creating a new post feature.
import http from "k6/http";
import { check } from "k6";
export const options = {
stages: [
{ duration: "10m", target: 200 }, // traffic ramp-up from 1 to a higher 200 users over 10 minutes.
{ duration: "30m", target: 200 }, // stay at higher 200 users for 30 minutes
{ duration: "5m", target: 0 }, // ramp-down to 0 users
],
};
// function for generating random string
const generateRandomString = (start, end) => {
const res = Math.random().toString(36).substring(start, end);
return res;
};
// function for generating random ID
const generateRandomId = () => {
const id = Math.floor(Math.random() * 1000);
return id;
};
export default function () {
// prepare request body
const requestBody = {
title: generateRandomString(1, 7),
body: generateRandomString(1, 20),
userId: generateRandomId(),
};
// sends POST request
const response = http.post(
"https://jsonplaceholder.typicode.com/posts",
JSON.stringify(requestBody),
{
headers: {
"Content-Type": "application/json",
Accept: "application/json",
},
}
);
// validate the response
check(response, {
"is status 201": (r) => r.status === 201,
"is not null": (r) => r.json() !== null,
"is contains valid id": (r) => r.json().id > 0,
"is contains valid title": (r) => r.json().title !== "",
"is contains valid body": (r) => r.json().body !== "",
});
}
Spike Testing
The spike testing ensures the system is operational with suddenly heavy workloads. Examples of sudden heavy workloads are product launch events, limited-time discount,s and others. During spike testing, the heavy workloads increased suddenly without any ramp-down or "pause time".
This is an example of spike testing for updating a post feature.
import http from "k6/http";
import { check } from "k6";
export const options = {
stages: [
{ duration: "2m", target: 500 }, // fast ramp-up without any break
{ duration: "1m", target: 0 }, // quick ramp-down
],
};
// function for generating random string
const generateRandomString = (start, end) => {
const res = Math.random().toString(36).substring(start, end);
return res;
};
// function for generating random ID
const generateRandomId = () => {
const id = Math.floor(Math.random() * 100);
return id;
};
export default function () {
const sampleId = generateRandomId();
// prepare request body
const requestBody = {
title: generateRandomString(1, 7),
body: generateRandomString(1, 20),
userId: sampleId,
};
// sends PUT request
const response = http.put(
`https://jsonplaceholder.typicode.com/posts/${sampleId}`,
JSON.stringify(requestBody),
{
headers: {
"Content-Type": "application/json",
Accept: "application/json",
},
}
);
// validate the response
check(response, {
"is status 200": (r) => r.status === 200,
"is not null": (r) => r.json() !== null,
"is contains valid id": (r) => r.json().id > 0,
"is contains valid title": (r) => r.json().title !== "",
"is contains valid body": (r) => r.json().body !== "",
});
}
Endurance Testing
The endurance testing (also known as soak testing) ensures the system is operational with average loads for a long duration. This test measures the performance degradation and the availability and stability during long periods.
The endurance testing can be executed after the smoke and average load tests are executed. The duration of this test must be longer than any other kind of test.
Actually, the typical duration is in hours like 3,4,12, and up to 72 hours. For this example, the duration is reduced.
This is an example of endurance testing for creating a post feature.
import http from "k6/http";
import { check } from "k6";
export const options = {
stages: [
{ duration: "10m", target: 100 }, // traffic ramp-up from 1 to 100 users over 10 minutes.
{ duration: "30m", target: 100 }, // stay at 100 users for 30 minutes
{ duration: "10m", target: 0 }, // ramp-down to 0 users
],
};
// function for generating random string
const generateRandomString = (start, end) => {
const res = Math.random().toString(36).substring(start, end);
return res;
};
// function for generating random ID
const generateRandomId = () => {
const id = Math.floor(Math.random() * 1000);
return id;
};
export default function () {
// prepare request body
const requestBody = {
title: generateRandomString(1, 7),
body: generateRandomString(1, 20),
userId: generateRandomId(),
};
// sends POST request
const response = http.post(
"https://jsonplaceholder.typicode.com/posts",
JSON.stringify(requestBody),
{
headers: {
"Content-Type": "application/json",
Accept: "application/json",
},
}
);
// validate the response
check(response, {
"is status 201": (r) => r.status === 201,
"is not null": (r) => r.json() !== null,
"is contains valid id": (r) => r.json().id > 0,
"is contains valid title": (r) => r.json().title !== "",
"is contains valid body": (r) => r.json().body !== "",
});
}
Using k6 Additional Libraries
The k6 provides additional Javascript libraries for enhancing testing processes like additional utilities, assertions, and others.
Assertion with k6chaijs
The assertion is a process to validate the response. The assertion is done by comparing the expected and the actual value. The assertion process can be done easily and comprehensively using k6chaijs
. This library enables chai
style assertion in the k6 test script.
In this example, the assertion is implemented using k6chaijs
for testing the create post feature.
import http from "k6/http";
import {
describe,
expect,
} from "https://jslib.k6.io/k6chaijs/4.5.0.1/index.js";
import {
randomIntBetween,
randomString,
} from "https://jslib.k6.io/k6-utils/1.4.0/index.js";
export const options = {
vus: 10,
duration: "30s",
};
export default function () {
describe("Create a new post", () => {
// prepare request body
const requestBody = {
title: randomString(20),
body: randomString(200),
userId: randomIntBetween(1, 99),
};
// sends POST request
const response = http.post(
"https://jsonplaceholder.typicode.com/posts",
JSON.stringify(requestBody),
{
headers: {
"Content-Type": "application/json",
Accept: "application/json",
},
}
);
// convert to JSON
const jsonResponse = response.json();
// perform assertions with k6chaijs
expect(response.status, "response status").to.equal(201);
expect(response).to.have.validJsonBody();
expect(jsonResponse.id, "post ID").to.be.above(0);
expect(jsonResponse.title, "title").to.not.equal(null);
expect(jsonResponse.body, "body").to.not.equal(null);
expect(jsonResponse.userId, "user ID").to.be.above(0);
});
}
Data-driven Test with Papaparse
The data-driven test in k6 can be implemented using Papaparse. Papaparse is a library for reading and processing CSV files.
In this example, the data-driven test is implemented for testing the update post feature.
The sample CSV file is available in the Github repository.
First, create an additional helper for retrieving random posts inside the random.js
file in the helpers
directory. The helpers
directory is created inside the src
directory.
export function getRandomPost(csvData) {
let randIndex = getRandomInt(0, csvData.length);
return {
postData: csvData[randIndex],
id: randIndex,
};
}
function getRandomInt(min, max) {
min = Math.ceil(min);
max = Math.floor(max);
return Math.floor(Math.random() * (max - min)) + min;
}
Then, create a test script for testing the update post feature. In this script, the Papaparse library is utilized for reading the CSV file enabling data-driven testing.
import http from "k6/http";
import { SharedArray } from "k6/data";
import papaparse from "https://jslib.k6.io/papaparse/5.1.1/index.js";
import {
describe,
expect,
} from "https://jslib.k6.io/k6chaijs/4.3.4.3/index.js";
import { getRandomPost } from "./helper/random.js";
export const options = {
vus: 20,
duration: "30s",
};
const csvData = new SharedArray("sample user dataset", () => {
// load CSV file
return papaparse.parse(open("../resources/posts.csv"), { header: true }).data;
});
export default function () {
describe("Update a post", () => {
// get random post data from CSV
const { postData, id } = getRandomPost(csvData);
// prepare request body
const requestBody = {
title: postData.title,
body: postData.body,
userId: postData.userId,
};
// sends PUT request
const response = http.put(
`https://jsonplaceholder.typicode.com/posts/${id}`,
JSON.stringify(requestBody),
{
headers: {
"Content-Type": "application/json",
Accept: "application/json",
},
}
);
const jsonResponse = response.json();
// perform assertions with k6chaijs
expect(response.status, "response status").to.equal(200);
expect(response).to.have.validJsonBody();
expect(jsonResponse.id, "post ID").to.be.above(0);
expect(jsonResponse.title, "title").to.not.equal(null);
expect(jsonResponse.body, "body").to.not.equal(null);
expect(jsonResponse.userId, "user ID").to.be.above(0);
});
}
Resources
- k6 Installation
- Configuring k6
- k6 Documentation
- Load Test Types
k6chaijs
Github Pagepapaparse
Documentation- k6 Additional Libraries
- Performance Testing with k6 Example Github Repository
I hope this article helps you to learn about API performance testing using k6.
Do you have any experience working on performance testing using k6? Please let me know in the comments down below 👇
Thank you
Top comments (0)