The magic of Gatling is that it models user behavior so closely, it feels more like observing real users than running a synthetic test.
Let’s see it in action. Imagine we want to test a simple login flow. Here’s a Gatling simulation that does just that:
import io.gatling.core.Predef._
import io.gatling.http.Predef._
import scala.concurrent.duration._
class BasicLoginSimulation extends Simulation {
val httpProtocol = http
.baseUrl("http://localhost:8080") // Base URL for all requests
.inferHtmlResources() // Automatically record and replay static resources
val scn = scenario("Login Scenario")
.exec(http("Login Request")
.post("/login")
.formParam("username", "testuser")
.formParam("password", "password123")
.check(status.is(200)) // Assert that the response status is 200 OK
.check(jsonPath("$.token").saveAs("authToken")) // Extract a token and save it
)
.exec(http("Fetch User Data")
.get("/users/me")
.header("Authorization", "Bearer #{authToken}") // Use the saved token
.check(status.is(200))
)
setUp(
scn.inject(
rampUsers(100) during (30 seconds) // Gradually inject 100 users over 30 seconds
).protocols(httpProtocol)
)
}
This BasicLoginSimulation is built around a scenario named "Login Scenario." Inside, we define a sequence of exec blocks, which represent individual user actions. The first exec is a post request to /login, sending username and password as form parameters. We then check if the status is 200 (meaning success) and, importantly, we use jsonPath("$.token").saveAs("authToken") to extract a token from the JSON response and store it in a variable called authToken. The subsequent exec block uses this authToken in the Authorization header for a GET request to /users/me.
The setUp block is where we define how many users will run this scenario and for how long. rampUsers(100) during (30 seconds) means Gatling will start with 0 users and, over 30 seconds, ramp up to 100 concurrent users who will then continuously execute the scenario. The .protocols(httpProtocol) links our scenario to the base URL and other HTTP configurations defined earlier.
The core problem Gatling solves is providing realistic performance insights without the overhead and complexity of traditional, often brittle, load testing tools. Instead of just hammering a server, Gatling simulates individual user sessions, including their thinking time, and uses a reactive, non-blocking architecture to handle a massive number of concurrent users efficiently. This means your test results more accurately reflect how real users interact with your application and how it performs under genuine load.
The httpProtocol object is your central hub for configuring HTTP requests. You can set a baseUrl to avoid repeating it in every request, and inferHtmlResources() is a powerful shortcut that tells Gatling to automatically record and replay all the static assets (like CSS, JavaScript, images) that a browser would fetch for a given page. This makes your simulations much more representative of actual user experience.
The check method is where the real intelligence of Gatling shines. It allows you to assert conditions on the responses received. status.is(200) is a basic but crucial check. jsonPath("$.token").saveAs("authToken") is an example of a more advanced check, extracting specific data from a JSON response and making it available for subsequent requests within the same user session. This is how you handle dynamic content and authenticated sessions.
When you run this simulation, Gatling will generate a detailed HTML report. This report shows metrics like response times, throughput, errors, and a breakdown of user activity over time. You’ll see graphs illustrating how your application scales as more users are injected, helping you identify bottlenecks and performance regressions.
What most people don’t realize is that Gatling’s DSL (Domain Specific Language) is Scala. This means you’re not limited to simple request sequences. You can leverage the full power of Scala to build incredibly complex and dynamic test scenarios. You can write custom logic, integrate with external data sources, and even define sophisticated conditional flows based on response data, making your load tests as intelligent as your application.
The next step is exploring how to handle different types of authentication and data-driven testing.