Load Testing with Gatling – The Complete Guide (Part 2)

The translation of the material was prepared on the eve of the start of the online course “Stress Testing”.


4. Application under test – video game database

In the rest of this tutorial, we will write tests for video game databases (Video Game Database). This app is, you guessed it, a fictional video game database. It boasts a simple API documented with Swagger, which covers all HTTP commands (Get, Put, Update, Delete) and supports XML and JSON payloads.

I recommend you clone video game database and run it locally. To do this, first clone (or download) repository and open a terminal in the location where you saved it on your computer. From there, you can launch the application with:

  • Gradle./gradlew bootRun

  • Mavenmvn spring-boot:run

After a few seconds, the application will load, and you can navigate to it at this address: http: // localhost: 8080 / swagger-ui / index.html # /

Here you should see the Swagger home page for the video game database, which should look something like this:

Video Game Database
Video Game Database

I advise you to poke the API a little through Swagger, trying various calls with XML and JSON.

Note: If you don’t want (or can’t) download and run the app locally, I also posted a version video game databases on AWS… The reason I highly recommend running the application locally is because anyone with that AWS address can manipulate the data in the database, so you will most likely see data different from what is shown in this tutorial!

Now that we have access to the video game database, we can start writing Gatling scripts.


5. Basics of Gatling Scripting

For the rest of this tutorial, we will write load testing scripts in Gatling to explore some of the basic concepts their development. Let’s start by creating a new one package inside folder scala our project, which will store your Gatling scripts. Name the package simulations.

5.1 Basic structure of a Gatling script

Inside the package, create a new class Scala With name MyFirstTest… Add the following code there:

package simulations

import io.gatling.core.Predef._
import io.gatling.http.Predef._

class MyFirstTest extends Simulation {
  
  // 1 Http Conf (конфигурация Http) 
  val httpConf = http.baseUrl("https://localhost:8080/app/")
    .header("Accept", "application/json")
    .proxy(Proxy("localhost", 8888))
  
  // 2 Scenario Definition (определение сценария)
  val scn = scenario("My First Test")
    .exec(http("Get All Games")
      .get("videogames"))
  
  // 3 Load Scenario (сценарий нагрузки)
  setUp(
    scn.inject(atOnceUsers(1))
  ).protocols(httpConf)
}

This is essentially the simplest Gatling script we can write. It makes one single call to the endpoint at http: // localhost: 8080 / app / videogames

Let’s discuss each part of the script in order:

5.1.1 Import statements

We’ve added these two import statements to the top of the script:

import io.gatling.core.Predef._
import io.gatling.http.Predef._

The Gatling packages are actually imported here – they both are required for all Gatling scripts

5.1.2 Extends Simulation

Then we see that our class Scala extends the class Gatling Simulation:

class MyFirstTest extends Simulation {

Again, to create a Gatling script, we must always extend from the Simulation class of the Gatling package.

Let’s go directly to the code inside the class, which is divided into 3 separate sections:

5.1.3 HTTP Configuration (Http Conf)

The first thing we do is set up HTTP configuration for our Gatling script.

The HTTP configuration in our class looks like this:

// 1 Http Conf
val httpConf = http.baseUrl("https://localhost:8080/app/")
    .header("Accept", "application/json")

Here we define baseUrlwhich will be used in all of our subsequent API calls in the script.

We also set in the default header (header) that will be sent on every call, Accept -> application / json

Other elements can be defined in the HTTP configuration – see Gatling HTTP Configuration Documentation

5.1.4 Scenario Definition

Section script definitions this is the place in our Gatling script where we define our user journey. These are the steps the user will follow when interacting with our application, for example:

  • Go to this page

  • Wait 5 seconds

  • Then go to another page

  • Then form a POST request with some data using this form

  • etc.

The entire definition of our script in this script will consist of a single GET call to the videogames endpoint. Note that the full endpoint is called http: // localhost: 8080 / app / videogamessince we have defined baseUrl in the HTTP config above:

// 2 Scenario Definition (определение сценария)
val scn = scenario("My First Test")
          .exec(http("Get All Games")
              .get("videogames"))

5.1.5 – Load scenario

The third and final part of the Gatling script is load scenario… This is where we set the load profile (like the number of virtual users, how long to run, etc.) for our Gatling test. Each of the virtual users will execute the script that we defined in the second part described above. In this example we are creating total one user with one iteration

// 3 Load Scenario (сценарий нагрузки)
setUp(
   scn.inject(atOnceUsers(1))
   ).protocols(httpConf)

Here we have a fairly primitive Gatling script! We can run it by running the class Engine and selecting the script MyFirstTest:

My first gatling test
My first gatling test

These 3 parts form the basic structure of all Gatling scripts:

  • HTTP configuration

  • Scenario definition

  • Load scenario

Let’s create a couple more scripts that explore the functionality of Gatling:

5.2 Pause and check response codes

In this script, we will add pause between requests. We will also check the received HTTP response code.

Create a new Scala class in the folder simulations entitled CheckResponseCode… Add the following code:

package simulations

import io.gatling.core.Predef._
import io.gatling.http.Predef._

import scala.concurrent.duration.DurationInt

class CheckResponseCode extends Simulation {

  val httpConf = http.baseUrl("https://localhost:8080/app/")
    .header("Accept", "application/json")

  val scn = scenario("Video Game DB - 3 calls")

    .exec(http("Get all video games - 1st call")
      .get("videogames")
      .check(status.is(200)))
    .pause(5)

    .exec(http("Get specific game")
      .get("videogames/1")
      .check(status.in(200 to 210)))
    .pause(1, 20)

    .exec(http("Get all Video games - 2nd call")
      .get("videogames")
      .check(status.not(404), status.not(500)))
    .pause(3000.milliseconds)

  setUp(
    scn.inject(atOnceUsers(1))
  ).protocols(httpConf)

}

5.2.1 Pauses

In the above script, we are making 3 API calls. One to videogamesthen one to videogames / 1and then another one to videogames

We’ve added different pauses at the end of each call.

First in line eighteen we added .pause(5) is a pause for 5 seconds.

Then in the line 24 We have done .pause(1, 20) is a pause for accidental time from 1 to 20 seconds.

Finally, in the line 29 we have .pause(3000.milliseconds) – as you might guess, this paused the Gatling script for 3000 milliseconds. Note: For Gatling to recognize milliseconds, we need to import scala.concurrent.duration.DurationInt to our class.

5.2.2 Checking response codes

For each of our API calls, Gatling checks the response code that comes back to us. If the response code does not match the assertion, Gatling will throw an error.

In line 17 we wrote .check(status.is(200))) – to check that the response code is 200.

Then in the line 23 we have .check(status.in(200 to 210))), which checks if the response code is in the range 200 to 210.

Finally, in the line 28 we check that the response code was NOT something from .check(status.not(404, status.not(500))) – to check that the response code is not 404 or 500.

5.3 Correlation in Gatling with the Check API

The Check API in Gatling does two things:

  • Asserted that the response contains some expected data.

  • Extracting data from this answer.

In this code example, we will use JSONPath to test some text in the response body. We will then use JSONPath in another call to retrieve some data from the response body and store that data in variable… And then we will use this variable in the next API call.

This process is called correlation and is a common scenario in load testing and performance testing.

Create a new script in the folder simulations With name CheckResponseBodyAndExtract… Here is its code:

package simulations

import io.gatling.core.Predef._
import io.gatling.http.Predef._

class CheckResponseBodyAndExtract extends Simulation {

  val httpConf = http.baseUrl("https://localhost:8080/app/")
    .header("Accept", "application/json")

  val scn = scenario("Check JSON Path")

      // First call - check the name of the game
      .exec(http("Get specific game")
      .get("videogames/1")
      .check(jsonPath("$.name").is("Resident Evil 4")))

      // Second call - extract the ID of a game and save it to a variable called gameId
      .exec(http("Get all video games")
      .get("videogames")
      .check(jsonPath("$[1].id").saveAs("gameId")))

      // Third call - use the gameId variable saved from the above call
      .exec(http("Get specific game")
      .get("videogames/${gameId}")
      .check(jsonPath("$.name").is("Gran Turismo 3"))

  setUp(
    scn.inject(atOnceUsers(1))
  ).protocols(httpConf)

}

Here we are making 3 different API calls. In the first call, we use JSONPath to check that the value by key name in the returned JSON matches Resident evil 4 :

  .exec(http("Get specific game")
  .get("videogames/1")
  .check(jsonPath("$.name").is("Resident Evil 4")))

The above challenge was just a validation and assertion of the name of the game. In the second call, we are actually retrieving the value and storing it in a variable. We extract id second game returned in JSON (i.e. game with index 1):

  .exec(http("Get all video games")
  .get("videogames")
  .check(jsonPath("$[1].id").saveAs("gameId")))

Now that we have a variable gameId, we can use it in the URL of the third API call:

  .exec(http("Get specific game")
  .get("videogames/${gameId}")
  .check(jsonPath("$.name").is("Gran Turismo 3"))

This is how you can perform correlation in Gatling by extracting data using the method .check()and then storing into a variable with .saveAs

You can find out more about Check API in the Gatling documentation.

5.4. Reusing Code in Gatling with Methods and Loop Calls

In the example scripts we’ve seen so far, we write all the code for each HTTP call individually. If we want to make another HTTP call to the same endpoint, it would be better to convert that code into a method.

This makes the steps in our script more understandable, since the logic of each step is abstracted.

Create a new script in the folder simulations under the name CodeReuseWithObjects… Here is its code:

package simulations

import io.gatling.core.Predef._
import io.gatling.http.Predef._

class CodeReuseWithObjects extends Simulation {

  val httpConf = http.baseUrl("https://localhost:8080/app/")
    .header("Accept", "application/json")


  def getAllVideoGames() = {
    repeat(3) {
      exec(http("Get all video games - 1st call")
        .get("videogames")
        .check(status.is(200)))
    }
  }

  def getSpecificVideoGame() = {
    repeat(5) {
      exec(http("Get specific game")
        .get("videogames/1")
        .check(status.in(200 to 210)))
    }
  }

  val scn = scenario("Code reuse")
      .exec(getAllVideoGames())
      .pause(5)
      .exec(getSpecificVideoGame())
      .pause(5)
      .exec(getAllVideoGames())

  setUp(
    scn.inject(atOnceUsers(1))
  ).protocols(httpConf)

}

We have defined two different methods for the two different API calls we make in this script. The first one is for getAllVideoGames():

  def getAllVideoGames() = {
    repeat(3) {
      exec(http("Get all video games - 1st call")
        .get("videogames")
        .check(status.is(200)))
    }
  }

Please note that we have added a method repeat(3) – this means that the code inside the block will be repeated 3 times (that is, it will make 3 HTTP calls).

The second HTTP call is to get a specific game:

def getSpecificVideoGame() = {
    repeat(5) {
      exec(http("Get specific game")
        .get("videogames/1")
        .check(status.in(200 to 210)))
    }
  }

We added the method again repeat(5)to repeat this call 5 times.

We can now call these methods in our script along with pause between each method, and the script itself looks nicer:

  val scn = scenario("Code reuse")
      .exec(getAllVideoGames())
      .pause(5)
      .exec(getSpecificVideoGame())
      .pause(5)
      .exec(getAllVideoGames())

Using the above style is helpful when your scripts get more complex. This is especially useful if you frequently make the same or similar API calls in your Gatling script.


Learn more about the course “Stress Testing”.

View a recording of an open demo lesson “LoadRunner: Developing and Debugging a Load Testing Script”

Similar Posts

Leave a Reply