JavaScript

How to Perform Load Testing Using k6

Load testing is a critical part of ensuring that your application can handle the expected traffic and perform well under stress. The k6 framework is a modern, open-source tool designed for load testing. It is developer-friendly, highly extensible, and allows you to write tests in JavaScript. In this article, we will explore the process of setting up k6, conducting load tests, and analyzing performance metrics.

1. Setting Up k6

Before we can start writing and executing load tests, we need to install k6 on our system. k6 supports multiple installation methods, including package managers and Docker, making it accessible for different platforms.

1.1 Installing k6 On macOS (using Homebrew)

If you are using macOS, you can install k6 easily with Homebrew:

1
brew install k6

After installation, verify that k6 is installed correctly by running:

1
k6 version

If the installation is successful, k6 will output its version number.

2. Writing a Basic Load Test Script

Once k6 is installed, we can write our first load test script. k6 scripts are written in JavaScript and allow us to simulate multiple virtual users (VUs) making HTTP requests to test an application’s performance. Let’s create a simple test script to send HTTP requests to a sample API. Create a new test script named basic-test.js:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
import http from 'k6/http';
import { check, sleep } from 'k6';
 
// Define the test configuration
export const options = {
  vus: 10, // Virtual Users
  duration: '30s' // Test duration
};
 
// Main test function
export default function () {
  // Make an HTTP GET request
  const response = http.get('https://test-api.k6.io/public/crocodiles/');
 
  // Check if the response status is 200
  check(response, {
    'is status 200': (r) => r.status === 200,
  });
 
  // Simulate user think time
  sleep(1);
}

This k6 script is designed to perform a basic load test on the API endpoint https://test-api.k6.io/public/crocodiles/. It defines a test scenario where 10 virtual users (VUs) concurrently send HTTP GET requests to the specified endpoint for a duration of 30 seconds. Each virtual user executes the default function, which makes a request, checks the response status, and then pauses for 1 second before repeating the process.

The check() function validates whether the HTTP response status is 200 (OK), ensuring that the server is responding correctly. The sleep(1) function simulates real-world behavior by introducing a brief pause between requests to mimic a user’s “think time” before sending another request.

3. Running the Load Test

Now that we have our test script, we can execute the load test using the following command:

1
k6 run basic-test.js

k6 will generate a real-time output summarizing the test execution. The output typically includes:

  • Test Progress – Displays the number of active virtual users and the test duration.
  • Summary Statistics – Includes request count, average response time, and HTTP status code checks.

Example Output

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
     execution: local
        script: basic-test.js
        output: -
 
     scenarios: (100.00%) 1 scenario, 10 max VUs, 1m0s max duration (incl. graceful stop):
              * default: 10 looping VUs for 30s (gracefulStop: 30s)
 
 
     ✓ is status 200
 
     checks.........................: 100.00% 208 out of 208
     data_received..................: 217 kB  7.0 kB/s
     data_sent......................: 28 kB   915 B/s
     http_req_blocked...............: avg=185.76ms min=12µs     med=18µs     max=3.96s    p(90)=27µs    p(95)=286.14µs
     http_req_connecting............: avg=11.57ms  min=0s       med=0s       max=253.06ms p(90)=0s      p(95)=0s     
     http_req_duration..............: avg=276.31ms min=212.71ms med=249.61ms max=909.83ms p(90)=353.3ms p(95)=438.63ms
       { expected_response:true }...: avg=276.31ms min=212.71ms med=249.61ms max=909.83ms p(90)=353.3ms p(95)=438.63ms
     http_req_failed................: 0.00%   0 out of 208
     http_req_receiving.............: avg=514.95µs min=106µs    med=209µs    max=16.41ms  p(90)=752.9µs p(95)=1.28ms 
     http_req_sending...............: avg=97.15µs  min=37µs     med=58µs     max=2.06ms   p(90)=125.3µs p(95)=185.89µs
     http_req_tls_handshaking.......: avg=38.94ms  min=0s       med=0s       max=903.19ms p(90)=0s      p(95)=0s     
     http_req_waiting...............: avg=275.69ms min=212.43ms med=249.14ms max=909.62ms p(90)=353ms   p(95)=438.31ms
     http_reqs......................: 208     6.716055/s
     iteration_duration.............: avg=1.47s    min=1.21s    med=1.25s    max=5.41s    p(90)=1.43s   p(95)=1.87s  
     iterations.....................: 208     6.716055/s
     vus............................: 10      min=10         max=10
     vus_max........................: 10      min=10         max=10
 
 
running (0m31.0s), 00/10 VUs, 208 complete and 0 interrupted iterations
default ✓ [======================================] 10 VUs  30s

This output provides real-time progress, including the number of virtual users and completed test iterations.

4. Configuring Load Testing Scenarios

k6 allows us to simulate different types of load scenarios using configuration options.

4.1 Ramp-up and Ramp-down Scenario

We can simulate a gradual increase and decrease in traffic using stages:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
import http from 'k6/http';
import { check, sleep } from 'k6';
 
export const options = {
  stages: [
    { duration: '10s', target: 20 },
    { duration: '30s', target: 50 },
    { duration: '10s', target: 0 },
  ],
};
 
export default function () {
  let response = http.get('https://test-api.k6.io/public/crocodiles/');
   
  check(response, {
    'status is 200': (r) => r.status === 200,
  });
 
  sleep(1);
}

Unlike a constant load test, this script gradually increases and decreases the number of virtual users (VUs) over time, simulating real-world traffic patterns. The test consists of three phases: an initial ramp-up phase, a steady-state phase, and a ramp-down phase.

The options section defines the test’s load pattern using the stages configuration. Each stage specifies a duration and a target number of virtual users:

  • The first stage ({ duration: '10s', target: 20 }) increases the number of VUs from 0 to 20 over 10 seconds, simulating a gradual influx of users.
  • The second stage ({ duration: '30s', target: 50 }) maintains a high load of 50 virtual users for 30 seconds, representing peak traffic.
  • The final stage ({ duration: '10s', target: 0 }) gradually reduces the number of VUs back to 0 over 10 seconds, simulating users leaving the application.

This test helps analyze how an application handles increasing user load, whether it can sustain peak traffic, and how smoothly it recovers when load decreases.

4.2 Stress Testing with k6

Stress testing helps identify breaking points and performance bottlenecks by simulating heavy user loads. Below is an example of a heavy load simulation.

1
2
3
4
export const options = {
  vus: 100,
  duration: '1m',
};

This test simulates 100 concurrent users for 1 minute to push the system to its limits.

4.3 Threshold-Based Performance Test

In this test scenario, we set a performance threshold to ensure that 95% of all HTTP requests complete within 100 milliseconds. This helps evaluate the API’s response time under load and ensures it meets performance expectations. If the threshold is not met, k6 will mark the test as failed, indicating potential performance bottlenecks.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
import http from 'k6/http';
import { check, sleep } from 'k6';
 
export const options = {
  thresholds: {
    http_req_duration: ['p(95)<100'], // 95% of requests should be below 100ms
  },
};
 
export default function () {
  let response = http.get('https://test-api.k6.io/public/crocodiles/');
 
  check(response, {
    'status is 200': (r) => r.status === 200,
  });
 
  sleep(1);
}

In this test, we define thresholds within the options object to measure API response times. The key threshold condition p(95)<100 ensures that 95% of HTTP requests complete in under 100ms. If the response times exceed this limit, the test will fail, signaling performance degradation.

4.4 Testing Multiple Endpoints

In real-world applications, an API consists of multiple endpoints, such as user authentication, fetching data, and submitting forms. To evaluate overall system performance, we can modify our k6 script to test multiple endpoints within the same load test.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
import http from 'k6/http';
import { check, sleep } from 'k6';
 
export const options = {
  vus: 10,
  duration: '30s',
};
 
export default function () {
  let responses = [
    http.get('https://test-api.k6.io/posts/')
  ];
 
  check(responses[0], { 'Crocodile API is OK': (r) => r.status === 200 });
  check(responses[1], { 'Login API is OK': (r) => r.status === 200 });
  check(responses[2], { 'Posts API is OK': (r) => r.status === 200 });
 
  sleep(1);
}

In this script, we send HTTP requests to three different API endpoints within a single test iteration:

  • Fetching a list of crocodiles (/public/crocodiles/).
  • Testing user login (/user/login/).
  • Retrieving posts (/posts/).

By running this script, we can evaluate the performance of multiple endpoints at once, helping us identify any API endpoints that may be slower or prone to failures under load.

5. Analyzing Performance Metrics

k6 provides detailed metrics to help us analyze the performance of our application. Once a load test is executed using k6, the framework generates detailed performance metrics that help assess how well the system handles the load. We can identify performance bottlenecks by analyzing these key metrics, optimize server response times, and improve application scalability. Key metrics include:

  • HTTP Request Duration: Time taken to complete an HTTP request.
  • Requests per Second (RPS): Number of requests made per second.
  • Checks: Success rate of validations.
  • Virtual Users (VUs): Number of simulated users.

Example Metrics:

  • http_req_duration: Average request duration was 50.12ms.
  • http_reqs: 300 requests were made at a rate of 9.96 requests per second.
  • checks: 100% of the checks passed.

When running a test, k6 displays real-time statistics in the command line. However, for deeper analysis, we can export the results as a JSON report using:

1
k6 run --out json=test-results.json basic-test.js

This command saves the test results in JSON format, making it easier to visualize and analyze with external tools like Grafana.

6. Conclusion

In this article, we explored how to execute load tests using the k6 framework, covering the entire process from setting up k6 to writing and executing test scripts. We demonstrated different testing scenarios, including constant load testing, ramp-up and ramp-down load testing, and threshold-based performance testing. We also analyzed performance metrics using k6’s real-time output and JSON reports, helping identify response times, failure rates, and overall system behavior under load.

Additionally, we extended our testing approach to handle multiple API endpoints within a single test, simulating real-world usage patterns. In conclusion, k6 is a powerful tool for executing load tests and analyzing application performance. By setting up k6, writing test scripts, and analyzing results using built-in metrics, we can ensure applications are scalable and resilient under load.

7. Download the Source Code

This article explored load testing using the k6 framework.

Download
You can download the full source code of this example here: k6 framework load testing

Omozegie Aziegbe

Omos Aziegbe is a technical writer and web/application developer with a BSc in Computer Science and Software Engineering from the University of Bedfordshire. Specializing in Java enterprise applications with the Jakarta EE framework, Omos also works with HTML5, CSS, and JavaScript for web development. As a freelance web developer, Omos combines technical expertise with research and writing on topics such as software engineering, programming, web application development, computer science, and technology.
Subscribe
Notify of
guest


This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Back to top button