In general, not all developers do REST API performance testing ๐Ÿ”ฅ they developed. In my opinion that is because they do not found a tool that is easy to use and easy to learn. Always I am saying "Do not write your own performance testing tools". The industry has so many tools for testing performance, I think some of them will fit you. Think its crucial stage in testing, because I want that consumer of my API will have the best experience. ๐Ÿค—

Quite often at work, I have tasks to write REST API. Of course, I write unit tests, integration tests to prove my solution correctness. And always next stand the question, how to test the performance of your REST API. ๐Ÿค”

In my experience, I saw a lot of self-made performance testing solutions starting from console application which calls API in a loop (sounds crazy? ๐Ÿคช) and ends with multi-threaded API calls which crash by themselves at higher loads ๐Ÿ’ฅ. The worst issue when you create and use self-made tools, they are hard to share in the whole organization ๐Ÿข. You have to spend a lot of time supporting that tool, test it, document it and of course, develop new features. Have you so much time ๐Ÿ•˜? I did not. Sometimes the self-made tool is worth the time you spend on supporting and testing it.

I spend some time researching ๐Ÿ” easy tools for testing REST API. The main condition for the tool was easy to use and easy to learn. I found K6. This tool is a fully open-source written in Go (https://github.com/loadimpact/k6).

https://k6.io/

The best developer experience for load testing.
Open source load testing tool and SaaS for engineering teams.

K6 supports different types of testing ๐Ÿงช: smoke testing, load testing, stress testing, and soak testing. When you look at K6 features, CI integrations, and extensions it feels a very developer orientated tool. That is exactly what we looking for. ๐Ÿงก

In the following steps, we will create a simple REST API, generate a testing script, and run some performance testing using K6. ๐Ÿ‘

Build steps:

  • Install prerequisite tools
  • Create a Web API project
  • Enable Swagger documentation
  • Generate K6 tests from swagger.json
  • Run performance tests

Prerequisites ๐Ÿ”จ

Tools you have to install before create this example project:

I have tested Windows and Linux K6 (WSL on Windows) installation. You can choose at your preferences. When you install on Windows don`t forget to add K6 binaries path in your PATH that you can run it from any location.

Create a Web API project

Create a new .NET Core Web API project by using the Windows command prompt.

dotnet new webapi -n WeatherApi
Command to create new empty web api application

Open newly created application using Visual Studio Code by File -> Open Folder.

For testing purposes, we need to add some slow performance simulation code. Change controller code WeatherForecastController.cs by following. โ˜๏ธ

using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;

namespace WeatherApi.Controllers
{
    [ApiController]
    [Route("[controller]")]
    public class WeatherForecastController : ControllerBase
    {
        [HttpGet]
        public async Task<int> Get()
        {
            var randomizer = new Random();
            int randomDelay = randomizer.Next(0, 5);

            await Task.Delay(TimeSpan.FromSeconds(randomDelay));

            return randomDelay;
        }
    }
}

This code will create a synthetic delay to slow down API performance ๐ŸŒ. Try to run your application and call multiple times https://localhost:5001/weatherforecast from browser.

Enable Swagger documentation

To generate K6 testing script automatically we have to enable Swagger documentation for our API by adding NuGet package.

dotnet add WeatherApi.csproj package NSwag.AspNetCore
Install Swagger NuGet package

Change Startup.cs to enable Swagger documentation.

public void ConfigureServices(IServiceCollection services)
{
    services.AddControllers();
    // Register the Swagger services
    services.AddSwaggerDocument();
}

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }

    app.UseRouting();

    // Register the Swagger generator and the Swagger UI middlewares
    app.UseOpenApi();
    app.UseSwaggerUi3();
    
    app.UseEndpoints(endpoints =>
        endpoints.MapControllers());
}
Enable Swagger services and endpoints

Try to run application and open URL http://localhost:5000/swagger. If everything is configured correctly you will see your API documentation. Using this UI you can also do API calls.

Swagger UI

More documentation about Swagger you can find in Microsoft documentation At this point we finished with our API and start the performance testing stage.

Generate K6 tests from swagger.json

Before generating K6 tests we have to save locally our auto generated swagger.json file by opening URL http://localhost:5000/swagger/v1/swagger.json.

{
  "x-generator": "NSwag v13.6.1.0 (NJsonSchema v10.1.21.0 (Newtonsoft.Json v9.0.0.0))",
  "swagger": "2.0",
  "info": {
    "title": "My Title",
    "version": "1.0.0"
  },
  "host": "localhost:5000",
  "schemes": [
    "http"
  ],
  "produces": [
    "text/plain",
    "application/json",
    "text/json"
  ],
  "paths": {
    "/WeatherForecast": {
      "get": {
        "tags": [
          "WeatherForecast"
        ],
        "operationId": "WeatherForecast_Get",
        "responses": {
          "200": {
            "x-nullable": false,
            "description": "",
            "schema": {
              "type": "integer",
              "format": "int32"
            }
          }
        }
      }
    }
  }
}

To generate K6 tests we have to use openapi-generator-cli it is open sourced tool that allows us to generate API clients. In our case API client is K6.

Using command line open directory you placed swagger.json file. From this directory we will run openapi-generator-cli. ย I use a generator from Docker container by executing the following command.

docker run --rm -v ${PWD}:/local 
 openapitools/openapi-generator-cli
 generate -i /local/swagger.json -g k6 -o /local/k6-test/
 --skip-validate-spec
Executing openapi-generator-cli from Docker

After successfully executing in directory k6-test you will find test script script.js. The script is generated in Javascript language. As you will see there is a single test which calls our API and checks if response status is 200.

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

const BASE_URL = "https://localhost:5001";
// Sleep duration between successive requests.
// You might want to edit the value of this variable or remove calls to the sleep function on the script.
const SLEEP_DURATION = 1;
// Global variables should be initialized.

export default function() {
    group("/WeatherForecast", () => {
        let url = BASE_URL + `/WeatherForecast`;
        // Request No. 1
        let request = http.get(url);
        check(request, {
            "": (r) => r.status === 200
        });
        sleep(SLEEP_DURATION);
    });
}

What I point out that in generated script BASE_URL has one unnecessary slash https://localhost:5001/. Need to remove slash at the end.

Run performance tests ๐Ÿงช

Finally, we did all the required steps to test our API. To run K6 test script is quite simple. You have to execute following command. But don`t forget to run your application locally dotnet run.

k6 run script.js
Command for running test script using K6.

After executing you will get an error WARN[0002] Request Failed error="Get https://localhost:5001//WeatherForecast: x509: certificate signed by unknown authority", that is because locally it uses self signed certificate. K6 can skip certificate validation by adding parameter --insecure-skip-tls-verify.

 k6 run script.js --insecure-skip-tls-verify
Command for runnin test script using K6 and skip tls verification.

After executing the test script โฑ๏ธ you will get a full summary and aggregated testing metrics.

K6 testing results summary

But it is not so interesting, because the test executes our API only one time (summary parameter iterations). Let`s add some considerable scenarios. To define different load scenarios you have to add global options. The following code defines scenarios.

// Global variables should be initialized.
export let options = {
    stages: [
      { duration: '10s', target: 100 }, // below normal load
      { duration: '20s', target: 200 }, // normal load
      { duration: '10s', target: 300 }, // around the breaking point
      { duration: '10s', target: 400 }, // beyond the breaking point
    ],
  };

By executing these scenarios you will get more interesting results ๐Ÿš€. You can play with different options and test your API. What is really nice about K6 that they have multiple metrics output options, you can choose from cloud based to on premise running tools like InfluxDB.

Realtime performance metrics visualisation using InfluxDB and Chronograf

Happy performance testing!๏ธ ๐Ÿงช

[eof]