Gatling’s repeat and doWhile are not just for simple iteration; they’re powerful tools for creating complex, dynamic user flows that mimic real-world application behavior.
Let’s see this in action. Imagine you have a shopping cart scenario where a user adds items, and you want to simulate adding a variable number of items, up to a certain limit, based on the outcome of a previous step.
import io.gatling.core.Predef._
import io.gatling.http.Predef._
import scala.concurrent.duration._
class LoopSimulation extends Simulation {
val httpProtocol = http
.baseUrl("http://localhost:8080") // Replace with your actual base URL
.acceptHeader("application/json")
val scn = scenario("Add Items Loop")
.exec(http("Get Session")
.get("/api/session"))
.doWhile(session => session("itemCount").asOption[Int].getOrElse(0) < session("maxItems").as[Int]) { // Condition checks item count
exec(http("Add Item")
.post("/api/cart/item")
.body(StringBody("""{"itemId": "item-${random.nextInt(100)}"}"""))
.check(status.is(201))
.check(jsonPath("$.newItemCount").saveAs("itemCount"))) // Save the current item count
}
.exec(http("Checkout")
.post("/api/cart/checkout"))
setUp(
scn.inject(
atOnceUsers(1) // Inject 1 user
).protocols(httpProtocol)
).maxDuration(1.minute)
}
In this example, the doWhile loop continues as long as the itemCount variable (which is updated with the newItemCount from the response) is less than the maxItems variable (which we’d need to inject or set earlier in a real scenario, perhaps from a feeder). This demonstrates a dynamic loop where the number of iterations isn’t fixed beforehand.
The core problem Gatling’s looping constructs solve is simulating sequences of actions that are not linear. Real users don’t just click through a predefined path; they might retry an action, add multiple items until they reach a budget, or navigate back and forth based on search results. repeat and doWhile allow you to model these non-linear, conditional, and iterative behaviors precisely.
Internally, Gatling’s Scala DSL is evaluated at runtime. When a doWhile or repeat block is encountered, Gatling checks its condition. If the condition is met (for doWhile) or the iteration count is reached (for repeat), it executes the enclosed chain of actions. After each execution, for doWhile, it re-evaluates the condition. For repeat with a fixed count, it simply iterates. The state of the simulation, including session variables, is carried through these iterations, allowing subsequent steps within the loop to depend on the outcomes of previous ones.
You control the looping behavior through two primary mechanisms: repeat(count) and doWhile(condition). repeat takes a fixed integer count and executes the enclosed chain that many times. doWhile takes a function session => Boolean which is evaluated before each iteration. If the function returns true, the loop continues; otherwise, it stops. You can also use repeat with a times parameter for a more explicit fixed count.
The repeat block also offers a way to pass a counter into the session, which can be useful for tracking iterations. For example: repeat(5, "i") { exec { session => println(s"Iteration ${session("i").as[Int]}"); session } }. This would print "Iteration 0", "Iteration 1", …, "Iteration 4".
A subtle but powerful aspect of doWhile is that its condition is evaluated before the first iteration. This means if the condition is initially false, the loop body will never execute. This is crucial for scenarios where a certain state must be met before any looping actions should occur.
One common pattern you’ll encounter is using doWhile to poll for a resource to become available or a status to change. Instead of a fixed repeat or a potentially infinite loop, doWhile provides a safe, condition-based exit. This is often combined with pause within the loop to avoid overwhelming the target system.
The next logical step after mastering these loops is to explore how to combine them with Gatling’s feeder mechanisms to inject dynamic data for each iteration, creating even more realistic and varied user journeys.