In this post, I am going to show how to create load tests for a REST API application with the help of Gatling 2.
This will be a step-by-step guide — starting from integrating Gatling 2 using SBT plugin, creating/configuring
test scenarios, and all the way to running the Gatling tests.
Gatling 2 stress tool sends simultaneous requests following a specific scenario.
The scenario consists of requests to the service and response checks, so we can emulate the service usage under
a high load. At the end of a simulation, Gatling 2 provides users with a detailed HTML report (you can find one
here). Using Gatling 2
will help to find out how reliable a service is and how well it performs. Also, it may help you to find and
fix various performance issues before you encounter significant problems in production.
For the sake of demonstration, I am going to create a simulation for the service built in the
"Building the REST service with Scala" article.
You can see the service sources here.
The Gatling SBT plugin will be used to run
the tests with just one SBT command.
There are also other extensions which can be used to run the simulation, see the full list
here.
We will create the tests in 4 steps:
Create SBT project and configure dependencies
Specify the SBT project configuration
Generate test data for the load test
Create a test scenario
Step 1. Create SBT project and configure dependencies
Gatling 2 uses Akka actors, so we will add this dependency to build.sbt, also we will add akka-slf4j package for
logging, and lift-json for JSON serialization:
The scalaSource in Gatling determines the folder of the Gatling test sources. And the
System.getProperty("user.dir") is used here to get a full path to the project root directory.
Step 2. Specify the SBT project configuration
Place the following configuration into the application.conf file:
src/test/resources/application.conf
12345678910111213141516171819202122232425262728
service {
# Url to service host:
host = "http://localhost:8080"
# Endpoints base path:
api_link = "/customer"
}
scenario {
# Scenario repeat count:
repeat_count = 1
# Emulate the specific count of users for simulation:
thread_count = 2
# Percent of successful service responses
# when the simulation is considered to be successful:
percent_success = 100
}
# Test data:
data {
first_names = ["Andrey", "Vlad", "Alexandra"]
last_names = ["Litvinenko", "Belik", "Borte"]
birthdays = ["1956-01-24", "1995-02-28", "1983-12-16"]
}
Let's create a trait which will help us to get the configuration data from the application.conf file.
importakka.event.slf4j.SLF4JLoggingimportcom.typesafe.config.{ConfigException,ConfigFactory}importscala.util.TrytraitSimulationConfigextendsSLF4JLogging{/** * Application config object. */private[this]valconfig=ConfigFactory.load()/** * Gets the required string from the config file or throws * an exception if the string is not found. * * @param path path to string * @return string fetched by path */defgetRequiredString(path:String)={Try(config.getString(path)).getOrElse{handleError(path)}}/** * Gets the required int from the config file or throws * an exception if the int is not found. * * @param path path to int * @return int fetched by path */defgetRequiredInt(path:String)={Try(config.getInt(path)).getOrElse{handleError(path)}}/** * Gets the required string list from the config file or throws * an exception if the string list is not found. * * @param path path to string list * @return string list fetched by path */defgetRequiredStringList(path:String)={Try(config.getStringList(path)).getOrElse{handleError(path)}}private[this]defhandleError(path:String)={valerrMsg=s"Missing required configuration entry: $path"log.error(errMsg)thrownewConfigException.Missing(errMsg)}/** * URL for test. */valbaseURL=getRequiredString("service.host")/** * Endpoint link. */valcustomerLink=getRequiredString("service.api_link")/** * Scenario repeat count. */valrepeatCount=getRequiredInt("scenario.repeat_count")/** * Count of users for simulation. */valthreads=getRequiredInt("scenario.repeat_count")/** * Percent of successful service responses when * the simulation is considered to be successful. */valpercentSuccess=Try(config.getInt("scenario.percent_success")).getOrElse(100)}
Step 3. Generate test data for the load test
Let's add the Customer case class as a container for test data. The class should comply to the Customer data of the
tested service:
importjava.util.Date/** * Customer entity. * * @param id unique id * @param firstName first name * @param lastName last name * @param birthday date of birth */caseclassCustomer(id:Option[Long],firstName:String,lastName:String,birthday:Option[Date])
In order to separate test data configuration from the test scenario, let's add the base object for the class
containing the test scenario:
importing of io.gatling.core.Predef._ and io.gatling.http.Predef._
extending io.gatling.core.scenario.Simulation Gatling API base class
providing the actual scenario (requests to a tested service and response checks)
setting up the scenario settings
First, let's look at the sample class, which will help you understand the basic principles of creating the Gatling
scenario class:
12345678910111213141516171819
// essential imports for simulation:importio.gatling.core.Predef._importio.gatling.http.Predef._// simulation class:classCustomerSimulationextendsio.gatling.core.scenario.Simulation{//scenariovalscn=scenario("Simulation name").repeat(repeatCount){exec(http(session=>"Request description").post(fullEndpointLink)// full link to tested endpoint.check(statusisrightStatus)// successful status)}// set up the scenario and threads (users) count:setUp(scn.inject(atOnceUsers(usersCount)))}
Now let's create a simulation for the tested service, this simulation will:
Send CRUD requests to the service and check the response.
importio.gatling.core.Predef._importio.gatling.http.Predef._importnet.liftweb.json.Serializationimportcom.sysgears.example.rest.data.CustomerTestData._importscala.util.Random/** * Load test for the rest service. */classCustomerSimulationextendsio.gatling.core.scenario.SimulationwithSimulationConfig{/** * http configuration. */valhttpProtocol=http.baseURL(baseURL)/** * Set default formats for json parser. */implicitvalformats=net.liftweb.json.DefaultFormats/** * Returns a random customer in JSON format. * * @return customer in JSON format */defrandCustomer={StringBody(Serialization.write(customers(Random.nextInt(customers.size))))}/** * Generates a random search query with random params. * * @return generated query */defrandSearchQuery={def?+(s:String)={if(Random.nextBoolean())s+"&"else""}valcustomer=customers(Random.nextInt(customers.size))"?"+?+(s"firstName=${customer.firstName}")+?+(s"lastName=${customer.lastName}")+?+(s"birthday=${dateFormat.format(customer.birthday.get)}")}/** * Scenario for simulation. */valscn=scenario("Simulation for the customer service").repeat(repeatCount){exec(http(session=>"Post a customer").post(customerLink).body(randCustomer).check(statusis201).check(jsonPath("$.id").saveAs("id"))).exec(http(session=>"Search customers").get(customerLink+randSearchQuery).check(statusis200)).exec(http(session=>"Get customers").get(customerLink).check(statusis200)).exec(http(session=>"Get customer by id").get("/customer/${id}").check(statusis200)).exec(http(session=>"Put customer by id").put("/customer/${id}").body(randCustomer).check(statusis200)).exec(http(session=>"Delete customer").delete(customerLink+"/${id}").check(statusis200))}/** * Sets the scenario. */setUp(scn.inject(atOnceUsers(threads))).protocols(httpProtocol).assertions(global.successfulRequests.percent.is(percentSuccess))//Check test result}
We need to start the service before running the load tests. You can find the service jar with the config file in the
tested_service directory of the repository, make sure to set up proper mysql parameters in the application.conf
file in order for application to access your database server and don't forget to create an appropriate MySQL database.
Now you can start the jar using the following command: