1

Introduction to performance testing with k6

 1 year ago
source link: https://wanago.io/2022/07/04/performance-testing-k6/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

Introduction to performance testing with k6

JavaScript Testing

July 4, 2022

Writing unit and integration tests is a great way to verify the business logic in our code. Unfortunately, it might not be enough to ensure that our application works as expected in a real-life scenario. When hundreds, thousands, or millions of users use our API, it strains it. We simulate the above circumstances through performance testing to help us analyze and fix errors and bottlenecks caused by heavy traffic.

Our system can perform differently under stress. Users can be unforgiving, and an unreliable website can hurt the business. Trying to reanimate a crashed application under pressure is not a pleasant experience. With the popularity of agile development, we strive to release new versions often. Moreover, making a seemingly unimpactful change can affect our system’s capability to handle a heavy load. Instead of dealing with performance-related downtime and trying to save the company’s reputation, it might be worth investing in performance testing instead.

One of the tools out there that can do the job of stress-testing is k6. Once we install it on our machine, we can start writing our tests.

Since we want to write our tests with TypeScript, we can install type definitions to make sure our IDE of choice understands

npm install @types/k6

There are a few types of tests that we can write using k6. Let’s go through each and gradually learn more about the tool.

Smoke testing

The idea behind smoke testing is to ensure our system can handle a minimal load. It might be worth running it as a sanity check.

The phrase smoke test originated from electronic hardware tests. If the device does not emit smoke when plugged in, it passes the smoke tests.

smokeTest.ts
import { get } from 'k6/http';
import { Options } from 'k6/options';
export const options: Options = {
  vus: 1,
  duration: '10s',
export default () => {
  get(`${__ENV.API_URL}/posts`);

In this article, we test the API created during the API with NestJS series.

Above are a few significant things to notice about our file structure. It exports the options object that we can use to alter the behavior of the test. So far, we’ve used a straightforward configuration:

  • vus: a number of virtual users calling our API at the same time (1 is the default value),
  • duration: how long we want our test to run.

Check out the official documentation for a complete list of possible options.

Also, we need to put the code of our test in a function and use a default export on it. Inside, we use an environment variable accessible through the __ENV object. To provide it, we need the --env option.

tsc smokeTest.ts && k6 run ./smokeTest.js --env API_URL=http://localhost:3000

Running the above gets us a long result that we can interpret:

results.png

For example, from the above data, we can see that the test made 9357 HTTP requests in 10 seconds, and each one was successful.

Load testing

The role of load testing is to assess the performance of the API. It can help us ensure that our system meets the goals of concurrent users and requests per second. It might be a good idea to run it as a part of a CI/CD pipeline.

Let’s write a test that logs into our system using real credentials and fetches the user’s data.

loadTest.ts
import { get, post } from 'k6/http';
import { check, sleep } from 'k6';
import { Options } from 'k6/options';
export const options: Options = {
  stages: [
    { duration: '5m', target: 100 },
    { duration: '10m', target: 100 },
    { duration: '5m', target: 0 },
  thresholds: {
    http_req_duration: ['p(99)<1500'],
export default () => {
  const loginResponse = post(`${__ENV.API_URL}/authentication/log-in`, {
    email: __ENV.USER_EMAIL,
    password: __ENV.USER_PASSWORD,
  check(loginResponse, {
    'logged in successfully': response => {
      return response.status === 200;
  const userDataResponse = get(`${__ENV.API_URL}/authentication`, {
    cookies: {
      Authentication: {
        value: loginResponse.cookies.Authentication[0].value,
  check(userDataResponse, {
    'got user data': response => {
      return response.status === 200;
  sleep(1);

We need to provide the necessary environment variables to run the above test.

tsc ./loadTest.ts && k6 run ./loadTest.js -e API_URL=http://localhost:3000 -e [email protected] -e USER_PASSWORD=1234567

-e  is short for --env

There are a few essential aspects of the above test. First, we define a set of options:

export const options: Options = {
  stages: [
    { duration: '5m', target: 100 },
    { duration: '10m', target: 100 },
    { duration: '5m', target: 0 },
  thresholds: {
    http_req_duration: ['p(99)<1500'],

In our options, we specify three stages with a different number of virtual users. Due to our configuration, k6 first gradually increases the traffic from one user to a hundred users over five minutes. Then, for five minutes, k6 stays flat on having a hundred users. Finally, it gradually decreases the number of users to 0 over the last five minutes.

Thanks to setting the threshold to http_req_duration: ['p(99)<1500'], we indicate that 99% of HTTP requests must complete below 1.5 seconds for our load test to be successful.

During our test, we attempt to log the user in and make sure it is a success using the check() function. Our authentication uses cookies, so we use the Set-Cookie header from the log-in response. We then use cookies to authenticate and fetch the information about the logged-in user. Finally, we wait one second before repeating the whole process.

If you want to know more about cookie-based authentication, check out API with NestJS #3. Authenticating users with bcrypt, Passport, JWT, and cookies

Stress testing

The goal of a stress test is to check the availability and stability of the system when under extreme conditions. We want to push the system to its limits and beyond. Thanks to doing that, we can determine what’s the maximum capacity of our system. It is also essential to make sure that when the system breaks, it can recover without manual intervention.

Remember that stress testing is about pushing the performance limits of our system, so don’t hold back.

stressTest.ts
import { batch } from 'k6/http';
import { sleep } from 'k6';
import { Options } from 'k6/options';
export const options: Options = {
  stages: [
    { duration: '2m', target: 100 },
    { duration: '5m', target: 100 },
    { duration: '2m', target: 300 },
    { duration: '5m', target: 300 },
    { duration: '2m', target: 500 },
    { duration: '5m', target: 500 },
    { duration: '2m', target: 700 },
    { duration: '5m', target: 700 },
    { duration: '10m', target: 0 },
export default function() {
  batch([
    ['GET', `${__ENV.API_URL}/posts`],
    ['GET', `${__ENV.API_URL}/categories`],
    ['GET', `${__ENV.API_URL}/comments`],
  sleep(1);
tsc ./stressTest.ts && k6 run ./stressTest.js -e API_URL=http://localhost:3000

When configuring the above stress test, we increase the load gradually instead of doing it in one big spike. Because of that, we define many different stages where we increase the load by 200 virtual users every two minutes and stay at this level for five minutes. By doing that, we can test whether our infrastructure scales up and down correctly.

Spike testing

When spike testing, we perform a stress test that spikes to an extreme load over a very short period. By doing that, we can check how our system performs under a sudden heavy load and if it recovers once it subsides. For example, imagine you’ve just aired a commercial for your company on one of the social media platforms. By doing a spike test, we can check how our application can handle it.

spikeTest.ts
import { batch } from 'k6/http';
import { sleep } from 'k6';
import { Options } from 'k6/options';
export const options: Options = {
  stages: [
    { duration: '10s', target: 100 },
    { duration: '1m', target: 100 },
    { duration: '10s', target: 3000 },
    { duration: '3m', target: 3000 },
    { duration: '10s', target: 100 },
    { duration: '3m', target: 100 },
    { duration: '10s', target: 0 },
export default function() {
  batch([
    ['GET', `${__ENV.API_URL}/posts`],
    ['GET', `${__ENV.API_URL}/categories`],
    ['GET', `${__ENV.API_URL}/comments`],
  sleep(1);
tsc ./spikeTest.ts && k6 run ./spikeTest.js -e API_URL=http://localhost:3000

The best possible outcome is that the performance of our system isn’t affected by the sudden traffic. The worst scenario is that the system crashes and does not recover after the traffic subsides.

Soak testing

We don’t put our system into extreme circumstances when performing a soak test. Instead, we test the system’s reliability over a long period. This can reveal various bugs, memory leaks, or infrastructure issues resulting from the system being under pressure for an extended period.

soakTest.ts
import { batch } from 'k6/http';
import { sleep } from 'k6';
import { Options } from 'k6/options';
export const options: Options = {
  stages: [
    { duration: '2m', target: 400 },
    { duration: '3h56m', target: 400 },
    { duration: '2m', target: 0 },
export default function() {
  batch([
    ['GET', `${__ENV.API_URL}/posts`],
    ['GET', `${__ENV.API_URL}/categories`],
    ['GET', `${__ENV.API_URL}/comments`],
  sleep(1);
tsc ./soakTest.ts && k6 run ./soakTest.js -e API_URL=http://localhost:3000

Summary

In this article, we’ve gone through the basics of various types of performance tests. In addition, we’ve learned the purpose of each kind of performance test and what issues it deals with. Thanks to doing that, we know it might be worth running a load test as a part of a CI/CD pipeline. The soak tests, on the contrary, take a very long time and can be very costly, so we might want to use them sparingly. Nonetheless, each type of performance test has its purpose and verifies different aspects of our application.


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK