API Load Testing using K6

Load testing is the process of testing an application's performance under various exterme conditions. It helps to identify bottlenecks and issues in time.

In this tutorial, we will show you how to perform API load testing using K6. K6 is an open-source modern load testing tool for developers.

Follow the steps below to get started with load testing using K6:

Installing K6

The first step is to install the K6 package:

For Linux/Debian/Ubuntu

Run the following four commands one by one:

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 Windows

To install K6 on Windows, open your command prompt and run the following command:

winget install k6

Note: Restart your computer after installation is complete.

For MacOS

To install K6 on MacOS using Homebrew:

brew install k6
For Fedora/CentOS

To install K6 on Fedora/CentOS, run the following two commands:

sudo dnf install https://dl.k6.io/rpm/repo.rpm
sudo dnf install k6

Performing API Load Testing

There are four major types of load testing. They are stress testing, smoke testing, soak testing, and load testing.

Stress Testing

Stress test is a type of load testing that helps to determine the limits of the system and verify the stability and reliability of the system under extreme conditions.

Stress testing an API or website is usually done to determine:

  • How your system will respond in extreme conditions.
  • Maximum capacity of your system to handle users or throughput.
  • The breaking point and failure mode of your system.
  • If your system will be able to recover without the need for manual intervention once the stress test is finished.

To perform stress testing, do the following:

  1. Create a JavaScript file with .js extension and add the code as shown in the example below:
  2. stress_load_testing.js
    
    import http from "k6/http";
    import { sleep } from "k6";
    
    export const options = {
      stages: [
        { duration: "2m", target: 100 }, // below normal load
        { duration: "5m", target: 100 },
        { duration: "2m", target: 200 }, // normal load
        { duration: "5m", target: 200 },
        { duration: "2m", target: 300 }, // around the breaking point
        { duration: "5m", target: 300 },
        { duration: "2m", target: 400 }, // beyond the breaking point
        { duration: "5m", target: 400 },
        { duration: "10m", target: 0 }, // scale down. Recovery stage.
      ],
    };
    
    export default function () {
      // make sure this is not production url
      const BASE_URL = "https://your-api-example.com";
      const params = {
        headers: {
          "Content-Type": "application/json",
        },
      };
      const payload = JSON.stringify({
        id: "1",
        startDate: "2022-01-01",
        endDate: "2022-05-31",
      });
    
      const responses = http.batch([
        ["POST", `${BASE_URL}/search`, payload, params],
        ["POST", `${BASE_URL}/search`, payload, params],
        ["POST", `${BASE_URL}/search`, payload, params],
        ["POST", `${BASE_URL}/search`, payload, params],
      ]);
    
      sleep(1);
    }
    

Smoke Testing

A smoke test is a standard load test with minimal load. Every time you develop a new script or edit an existing script, you should perform a smoke test as a sanity check.

Smoke testing is usually done to make sure your test script is free of errors and check that your system doesn't throw any errors when under minimal load.

Here's a sample smoke test script that authenticates the user:

smoke_load_testing.js

import http from "k6/http";
import { check, sleep } from "k6";

export const options = {
  vus: 1, // 1 user looping for 1 minute
  duration: "1m",
};

//make sure this is not production url
const BASE_URL = "http://your-api-example.com";
const USERNAME = "developerxyzp";
const PASSWORD = "Password$";

const payload = JSON.stringify({
  username: USERNAME,
  password: PASSWORD,
});

const header_params = {
  headers: {
    "Content-Type": "application/json",
  },
};

export default () => {
  const loginRes = http.post(`${BASE_URL}/users/login`, payload, header_params);

  console.log("Login Response : ", loginRes.status);
  check(loginRes, { "logged in successfully": (res) => res.status == 200 });

  sleep(1);
};

Soak Testing

Soak testing is another type of load testing, which is concerned with a system's reliability over a longer period of time. Soak testing helps to identify performance and reliability issues by putting the system under pressure for a longer period of time.

Bugs, memory leaks, insufficient storage quotas, wrong configuration, and infrastructure failures are all common causes of reliability concerns. Incorrect database tuning, memory leaks, resource leaks, or a big volume of data are all common causes of performance concerns.

A soak test allows you to mimic days of traffic in only a few hours.

Here's an example script for soak testing:

soak_load_testing.js

import http from 'k6/http';
import { sleep } from 'k6';

export const options = {
  stages: [
    { duration: '2m', target: 400 }, // ramp up to 400 users
    { duration: '2h56m', target: 400 }, // stay at 400 for ~3 hours
    { duration: '2m', target: 0 }, // scale down. (optional)
  ],
};
//make sure this is not production url
const API_BASE_URL = 'https://your-api-example.com';

export default function () {
  http.batch([
    ['GET', `${API_BASE_URL}/search/1`],
    ['GET', `${API_BASE_URL}/search/2`],
    ['GET', `${API_BASE_URL}/search/3`],
    ['GET', `${API_BASE_URL}/search/4`],
  ]);

  sleep(1);
}

Load Testing

A load testing is done to determine how a system behaves under normal and peak conditions. The load testing should ensure that a system behaves normally when a large number of users access it at the same time.

Here's an example script for load testing:

load_testing.js

import http from "k6/http";
import { check, sleep } from "k6";

export const options = {
  stages: [
    { duration: "5m", target: 100 }, // simulate ramp-up of traffic from 1 to 100 users over 5 minutes.
    { duration: "10m", target: 100 }, // stay at 100 users for 10 minutes
    { duration: "5m", target: 0 }, // ramp-down to 0 users
  ],
  thresholds: {
    http_req_duration: ["p(99)<1500"], // 99% of requests must complete below 1.5s
  },
};

//make sure this is not production url
const API_BASE_URL = "https://your-api-example.com";
const USERNAME = "developerxyzp";
const PASSWORD = "Password$";

export default () => {
  const payload = JSON.stringify({
    username: USERNAME,
    password: PASSWORD,
  });

  const header_params = {
    headers: {
      "Content-Type": "application/json",
    },
  };
  const loginRes = http.post(
    `${API_BASE_URL}/users/login`,
    payload,
    header_params
  );

  check(loginRes, {
    "logged in successfully": (resp) => resp.status == 200,
  });

  sleep(1);
};

Running K6 Test

Run the above .js test file using the K6 command:

k6 run smoke_load_testing.js

The K6 load testing results will look like the following:

Load testing output example

Here, checks represents the success rate, http_req_failed shows the proportion of requests that failed, http_reqs shows the total number of requests, and vus shows the number of users.