Load Testing a Rest Endpoint with Gatling


1. Introduction

In this article, we’ll look at how you can use Gatling to test the performance of any Rest endpoint, focusing on load testing. We’ll start with a brief introduction to the different types of performance testing and their key performance indicators (KPIs).

Next, you’ll get a general idea of ​​Gatling terminology. We’ll walk through an example using the Maven Gatling plugin and dependencies, and explore Gatling Java DSL for load testing with scenario simulation.

Finally, let’s run the simulation and look at the generated report.

2. Types of performance testing

Performance testing involves the measurement of various metrics in order to test the performance of a system under various levels of traffic and throughput. Other types of performance testing are load testing, stress testing, stability testing (soak testing), peak load testing (spike testing), and scalability testing. Let’s take a quick look at the purpose of each type of performance testing strategy.

Load testing involves testing the system under heavy load for a certain period of time. On the other hand, stress testing involves a gradual increase in the load on the system in order to find the point of its limiting point. During stability testing, stable traffic is passed through the system for a long time to identify bottlenecks. As you can see from the name, spike testing is to check the performance of the system, when the number of requests quickly increases to an overload level, and then decreases again. Finally, scalability testing is a test of system performance as the number of user requests increases or decreases.

When conducting performance testing, several KPIs can be collected to measure system performance. These include: transaction response time, throughput (the number of transactions processed in a given period), and errors (such as timeouts). Stress testing can also help identify memory leaks, slowdowns, security vulnerabilities, and data corruption.

In this article, we will focus on load testing with Gatling.

3. Key terms

Let’s start with the basic terms used in the context of working with the Gatling framework.

  • Scenario: A series of steps virtual users perform to replicate common user actions such as logging in or making a purchase.

  • Feeders: A mechanism that allows you to inject test data from external sources such as CSV or JSON files into virtual user activities.

  • Simulation: Specifies the number of virtual users that run the script during a given period of time.

  • Session: Each virtual user is backed by a session that keeps track of the messages exchanged during the script.

  • Recorder: The Gatling user interface provides a Recorder tool that generates a script and simulation.

4. Setting example

Let’s focus on a small part of the microservice employee managementconsisting of a RestController with POST and GET endpoints that need to be load tested.

Before proceeding with the implementation of our simple solution, let’s add the necessary gatling dependencies:

dependency>
    <groupId>io.gatling</groupId>
    <artifactId>gatling-app</artifactId>
    <version>3.7.2</version>
</dependency>
Copy
<dependency>
&lt;groupId&gt;io.gatling.highcharts&lt;/groupId&gt;
&lt;artifactId&gt;gatling-charts-highcharts&lt;/artifactId&gt;
&lt;version&gt;<span class="hljs-number" style="box-sizing: border-box; color: rgb(78, 147, 89);">3.7</span><span class="hljs-number" style="box-sizing: border-box; color: rgb(78, 147, 89);">.2</span>&lt;/version&gt;

</dependency>

Next, let’s add maven plugin:

<plugin>
    <groupId>io.gatling</groupId>
    <artifactId>gatling-maven-plugin</artifactId>
    <version>4.2.9</version>
    <configuration>
        <simulationClass>org.baeldung.EmployeeRegistrationSimulation</simulationClass>
    </configuration>
</plugin>

As the information in the pom.xml file shows, we explicitly set the mock class EmployeeRegistationSimulation in the plugin config. This means that the plugin will use the specified class as the basis for running mocks.

Next, let’s define a RestController with POST and GET endpoints that we want to test with Gatling:

@PostMapping(consumes = { MediaType.APPLICATION_JSON_VALUE })
public ResponseEntity<Void> addEmployee(@RequestBody EmployeeCreationRequest request, UriComponentsBuilder uriComponentsBuilder) {
    URI location = uriComponentsBuilder.path("/api/employees/{id}")
      .buildAndExpand("99")
      .toUri();
    return ResponseEntity.created(location)
      .build();
}

Next, let’s add a GET endpoint:

@GetMapping("/{id}")
public Employee getEmployeeWithId(@PathVariable("id") Long id) {
    List<Employee> allEmployees = createEmployees();
    return allEmployees.get(ThreadLocalRandom.current()
      .nextInt(0, allEmployees.size()));
}

Now consider the class Simulation and its components and APIs that enable load testing.

5. Simulation steps

The simulation is a load test that reflects various aspects, such as how multiple user groups can work, what scenarios they will execute, and how new virtual users will be introduced. In the Gatling framework, the class Simulation is the primary component that initiates the load testing process. Java Gatling API includes a mutable abstract class Simulation. We can extend the class Simulation tailored to specific requirements to create a custom simulation:

Public class EmployeeRegistrationSimulation extends Simulation {

    private static final HttpProtocolBuilder HTTP_PROTOCOL_BUILDER = setupProtocolForSimulation();

    private static final Iterator<Map<String, Object>> FEED_DATA = setupTestFeedData();

    private static final ScenarioBuilder POST_SCENARIO_BUILDER = buildPostScenario();

    // ...
}

Essentially, here we need to define the following:

Now let’s take a look at the individual steps and how we can define them using the DSL provided by Gatling. Let’s start with the protocol configuration.

5.1. HTTP protocol configuration

Gatling is a technology agnostic load testing tool. It supports various protocols including HTTP, HTTPS and WebSockets. In this section, we will focus on configuring the HTTP protocol for a load testing scenario.

To configure the HTTP protocol details in the EmployeeRegistrationSimulation class, we will use the HTTP DSL type, which serves as the entry point for the Gatling HTTP DSL. We then use the DSL HTTPProtocolBuilder to define the HTTP protocol configuration:

private static HttpProtocolBuilder setupProtocolForSimulation() {
    return HttpDsl.http.baseUrl("https://localhost:8080")
      .acceptHeader("application/json")
      .maxConnectionsPerHost(10)
      .userAgentHeader("Gatling/Performance Test");
}

Setting up the HTTP protocol in Gatling involves using the class HttpDsl to define the HTTP protocol configuration using the DSL HTTPProtocolBuilder. Key configuration options include baseUrl, acceptHeader, maxConnectionsPerHost And userAgentHeader. These settings help ensure that our load test accurately mimics real world scenarios.

5.2. Definition of feeders

Feeders are a convenient API that allows testers to enter data from external sources into user virtual sessions. Gatling supports various feeders such as CSV, JSON, file based feeders and array/list based feeders.

Next, let’s create a method that will return test data for the test case:

private static Iterator<Map<String, Object>> feedData() {
    Faker faker = new Faker();
    Iterator<Map<String, Object>> iterator;
    iterator = Stream.generate(() -> {
          Map<String, Object> stringObjectMap = new HashMap<>();
          stringObjectMap.put("empName", faker.name()
            .fullName());
          return stringObjectMap;
      })
      .iterator();
    return iterator;
}

Here we create a method that returns Iterator<Map<String, Object>> to get test data for our test case. Then the method feedData() generates test data using the Faker library, creates a HashMap to hold the data, and returns an iterator over that data.

Essentially, a feeder is a type alias for a component Iterator<Map<String, Object>>created by the method feed. Method feed polls records Map<String, Object> and injects their contents into the simulation script.

Gatling offers several strategies for embedded feeders such as queue(), random(), shuffle() And circular(). In addition, depending on the system under test, you can configure the data loading mechanism as eager() or batch().

5.3. Scenario Definition

A script in Gatling represents a typical user behavior that virtual users will follow. This is a workflow based on the resources defined in the Employee Controller. In this case, we’ll create a script that simulates creating an employee using a simple workflow:

private static ScenarioBuilder buildPostScenario() {
    return CoreDsl.scenario("Load Test Creating Employee")
      .feed(FEED_DATA)
      .exec(http("create-employee-request").post("/api/employees")
        .header("Content-Type," "application/json")
        .body(StringBody("{ \"empName\": \"${empName}\" }"))
        .check(status().is(201))
        .check(header("Location").saveAs("location")))
      .exec(http("get-employee-request").get(session -> session.getString("location"))
        .check(status().is(200)));
    }

The Gatling API provides a method scenario(String name)which returns an instance of the class ScenarioBuilder. ScenarioBuilder contains script details, including the source of the test data, and HTTP request details such as the request body, headers, and the expected status code.

Basically, we create a scenario where we first send a request using the post method to create an employee. The request body contains the value empName (in JSON format) obtained from the test data. The method also checks for the expected HTTP status code (201) and stores the value of the Location header in the session using the saveAs method.

The second request uses the get method to retrieve the created employee by sending the stored Location header value to the request URL. It also checks for the expected HTTP status code (200).

5.4. Load injection model

In addition to defining the scenario and protocol, the load injection model to be simulated needs to be defined. In our example, we will gradually increase the load by adding virtual users over time. Gatling provides two load injection models: open and closed. The open model allows us to control the arrival rate of virtual users, which is more in line with real systems.

Java API Gatling provides a class OpenInjectionStep, which encapsulates the common attributes and behavior of load models with open injection. There are three subclasses OpenInjectionStepthat can be used:

  • ConstantRateOpenInjection: This injection model maintains a constant arrival rate of virtual users.

  • RampRateOpenInjectionA: This injection model gradually increases the arrival rate of virtual users.

  • Композитная: This injection model allows you to combine different types of injection models.

For our example, we will use RampRateOpenInjection. We will start with 50 vUs and gradually increase the load by adding 50 vUs every 30 seconds until we reach 200 vUs. We will then keep the load at 200 virtual users for 5 minutes:

private RampRateOpenInjectionStep postEndpointInjectionProfile() {
    int totalDesiredUserCount = 200;
    double userRampUpPerInterval = 50;
    double rampUpIntervalSeconds = 30;
    int totalRampUptimeSeconds = 120;
    int steadyStateDurationSeconds = 300;

    return rampUsersPerSec(userRampUpPerInterval / (rampUpIntervalSeconds / 60)).to(totalDesiredUserCount)
      .during(Duration.ofSeconds(totalRampUptimeSeconds + steadyStateDurationSeconds));
}

By defining a load injection pattern, we can accurately model how the system will behave under different load levels. This will help identify performance bottlenecks and make sure our system can handle the expected user load.

5.5. Setting up the simulator

To set up the simulation, we will combine the protocol, script, and load injection model we defined earlier. This setting will be called from the class constructor EmployeeRegistrationSimulation:

public EmployeeRegistrationSimulation() {

    setUp(BUILD_POST_SCENARIO.injectOpen(postEndpointInjectionProfile())
      .protocols(HTTP_PROTOCOL_BUILDER));
}

5.6. Assertions

Finally, we use the Gatling DSL to verify that the simulation works as expected. Let’s get back to the constructor EmployeeRegistrationSimulation() and add some assertions to the existing method setup(...):

setUp(BUILD_POST_SCENARIO.injectOpen(postEndpointInjectionProfile())
  .protocols(HTTP_PROTOCOL_BUILDER))
  .assertions(
    global().responseTime().max().lte(10000),
    global().successfulRequests().percent().gt(90d)

As we can see, here we want to assert the following conditions:

  • The maximum response time according to the settings must be less than or equal to 10 seconds.

  • The percentage of successful requests must exceed 90.

6. Run the simulation and analyze the report

When we created our Gatling project, we used Maven with the plugin Gatling Maven Plugin. Therefore, we can use a Maven task to execute our Gatling test. To run a Gatling script through Maven, open a command prompt in your Gatling project folder:

mvn gatling:test

As a result, we will get the following metrics:

Finally, Gatling generates an HTML report in the target/gatling directory. The main file in this directory is index.html, which briefly describes the load test configuration, response time distribution graph and statistics for each request, as described above. Let’s look at some graphs from the report:

The requests per second graph helps us to understand how the system is coping with the growing level of traffic. By analyzing the requests per second graph, we can determine the optimal number of requests that the system can handle without performance degradation or errors. This information can be used to improve the scalability of the system so that it can handle expected levels of traffic.

Another interesting chart to look at is the response time ranges:

The response time distribution chart shows the percentage of requests that fall into certain response time groups, such as less than 800 milliseconds, 800 milliseconds to 1.2 seconds, and more than 1.2 seconds. We can see that all responses are within <800ms.

Let’s look at the response time percentiles:

The request statistics section shows detailed information about each request, including the number of requests, the percentage of successful requests, and various response time percentiles such as the 50th, 75th, 95th, and 99th percentiles. Overall, the index.html file provides a comprehensive summary of load testing results, making it easy to identify bottlenecks and performance issues.

7. Conclusion

In this article, we learned how to use the Gatling Java DSL to load test any REST endpoint.

First, we did a brief overview of the different types of performance testing. Then key terms specific to Gatling were introduced. We have demonstrated how to implement a load test on a POST endpoint while respecting the desired load and timing constraints. In addition, we can analyze test results to identify areas for improvement and optimization.

The full code for this article can be found at GitHub.


What stands can be used for load testing and what are their features? We will discuss this at an open lesson, which will be held tomorrow evening. We invite everyone! You can sign up for a lesson on the page of the course “Load Testing”.

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *