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 // 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 () { 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 () { 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 = [ ]; 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.
You can download the full source code of this example here: k6 framework load testing