API load testing using k6

Binod Mahto
5 min readJan 23, 2023

Grafana k6 is an open-source load testing tool. K6 allows you to test the reliability and performance of your APIs and catch performance regressions and problems earlier.

By reading this article, you will learn how to perform load testing on RESTful APIs using K6.

Setup
K6 has a standalone installer/package for Linux, macOS, and Windows and also supports a docker container.

Windows
For Windows users, please use the Choco (chocolatey package manager) or Winget (Window package manager, which default comes with Windows OS).
To install open a command prompt and run the command as

--for choco
choco install k6

--for winget
winget install k6

Alternatively, you can download and run the latest official installer.

If you are using Visual Studio Code, you can use the extension “k6 for Visual Studio Code” to execute your script directly from visual studio code.

Linux
For Linux Users, use the command below to install the k6.

--for Debian/Ubuntu
sudo gpg --no-default-keyring --keyring /usr/share/keyrings/k6-archive-keyring.gpg --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys C5AD17C747E3415A3642D57D77C6C491D6AC1D69
echo "deb [signed-by=/usr/share/keyrings/k6-archive-keyring.gpg] https://dl.k6.io/deb stable main" | sudo tee /etc/apt/sources.list.d/k6.list
sudo apt-get update
sudo apt-get install k6

--for Fedora/CentOS
sudo dnf install https://dl.k6.io/rpm/repo.rpm
sudo dnf install k6

MacOS
For MacOS users, install K6 using the Homebrew package installer.

brew install k6

For Window users, After installing the k6 package open a command prompt and type k6. If you get below output then you are ready.

Test Lifecycle
K6 test lifecycle has four stages and each stage runs in the same order.

  1. init context: The init context runs once per VU and is generally used for tasks such as, loading local files, importing modules, declaring lifecycle functions etc. It is required.
//1. init code

2. setup function: setup function runs once in the entire lifecycle of the test and is generally used for data setups for tests. It is optional.

export function setup() {
// 2. setup code
}

3. default function: default function depicts the test scenario, in short this is your test method. It is required.

export default function (data) {
// 3. VU code
}

4. teardown function: Like setup, teardown function also runs once in the entire lifecycle and is generally used for cleanup or postprocessing data stuff. It is optional.

If the Setup function ends abnormally (e.g throws an error), the teardown() function isn’t called. Consider adding logic to the setup() function to handle errors and ensure proper cleanup.

export function teardown(data) {
// 4. teardown code
}

Now let's write a test for REST API. For my tests here, I’m using Httpx library which is available here: https://jslib.k6.io/httpx/0.0.1/index.js

HTTP Get Request
Create your first script to test a Get REST request. To do this create a js file, for example k6-testscripts.js

//import modules
import { Httpx } from 'https://jslib.k6.io/httpx/0.0.1/index.js';
import { check } from "k6";
import { Rate } from 'k6/metrics';
//Error Rate is an object for representing a custom metric keeping track of
//the failures
export const errorRate = new Rate('errors');
//create httpx session
const session = new Httpx({
baseURL: {baseurl i.e. http://localhost:80/},
timeout: 10000 // 10s timeout.
});
//set headers
session.addHeaders(
{
'Authorization': 'Bearer {token}', //auth token
'User-Agent': 'My k6 custom user agent', //user agent, any suitable name
'Content-Type': 'application/json',
}
);

//test method
export default function getUserPermission() {
//get request
let res = session.get("user/permission?userid=binod");

//check response for 200 and response within 1000ms
check(res, {
"Is Status 200": (r) => r.status === 200,
"Getting User Permission response time": (r) => r.timings.duration < 1000,
}) || errorRate.add(1);

if (res.status === 200) {
let resp = JSON.parse(res.body);
console.log("response:", JSON.stringify(resp));//prints the result, optional
}
else
console.log(res.body);//this will help to know the error details on console. optional
}

Now let’s run the below script in the command prompt to execute the script:

k6 run C:\TestScripts\k6-testscripts.js

If all is well, the result of the above scripts would be as:

The above result says, the Get request took 427.15ms (http_req_duration) to complete and took 34.97ms to connect (http_req_connecting) for one HTTP request. (http_reqs).

Now the same Get request will continuously for 30 seconds and 30 VUs.

k6 run -d 30s -u 30 C:\TestScripts\k6-testscripts.js

In my case, the above command results:

Based on the above results, a total of 319 requests (http_reqs) have been made and out of 319 requests, response time checks failed for 30 requests as it took more than 1000ms but all succeeded successfully as far as success response concerns.

HTTP Post request
For post request the test scripts would be as:

export default function registerUser() {
let res = session.post(`/user/register/`, {
first_name: 'Binod',
last_name: 'Mahto',
username: 'binod',
});
//check response for 201 and response within 1000ms
check(res, {
"Is Status 201": (r) => r.status === 201,
"Registering User response time": (r) => r.timings.duration < 1000,
})
}

Complete Result output summary
Here are the complete lists of summaries generated from your request execution.

  1. checks: rate of successful checks percentage from total no. of checks, it also includes the no. of checks passed and failed.
  2. data_received: the amount of data received.
  3. data_sent: the amount of data sent.
  4. errors: rate of error percentage for failure checks.
  5. http_req_blocked : average time spent waiting for TCP connection
  6. http_req_connecting : average time spent establishing a TCP connection
  7. http_req_duration: average total time for the request. It’s calculated based on http_req_sending + http_req_waiting + http_req_receiving.
  8. http_req_failed: percentage of failed requests. in our case 0 (0.00%) failed out of 319 requests.
  9. http_req_receiving : average time spent on data receiving
  10. http_req_sending: average time spent on data sending
  11. http_req_tls_handshaking : average time spent on TLS handshaking
  12. http_req_waiting: average time spent on waiting for a response from the remote host
  13. http_reqs : total number of requests
  14. iteration_duration: average time it took to execute the default function for each iteration.
  15. iterations: total no. of iterations to execute default function (test method)
  16. vus: number of virtual users. It is also represented as VU.
  17. vus_max: maximum no. of virtual users allocated for the test execution.

httpx library
To know more about the httpx library to for other HTTP request as Put, Patch, Delete etc, please refer the doc here: https://k6.io/docs/javascript-api/jslib/httpx/post/

Hope you enjoyed the content, follow me for more like this, and please don’t forget to clap for it. Happy programming.

--

--

Binod Mahto

Solution Architect & Full Stack Developer. Passionate about Software designing & development and Learning Technologies and Love to share what I learn.