In the previous post, we made sure our checks actually catch real failures. Today we look at something just as important but easier to overlook, whether your scenario actually behaves like a real user in the first place.

A scenario that fires request after request with zero delay between them does not represent any real visitor to your site. It represents a script racing through steps as fast as the network allows. That produces load numbers, but not necessarily useful ones, since real traffic has gaps in it while people actually read a page, think about what to click next, or get distracted by something else entirely.

Basic Pauses

We have already used the simplest form of this, a fixed pause between steps.

.exec(http("View Product").get("/products/42"))
.pause(3)
.exec(http("Add To Cart").post("/cart/items"))

This waits exactly three seconds between the two requests. It is better than nothing, but every single virtual user pausing for exactly the same amount of time is itself a little unrealistic. Real users vary a lot in how long they take.

Randomized Pauses

Gatling supports a range instead of a fixed value, which spreads that pause out more naturally across your population of virtual users.

.exec(http("View Product").get("/products/42"))
.pause(2, 8)
.exec(http("Add To Cart").post("/cart/items"))

Now each user pauses somewhere between two and eight seconds, picked randomly. Across thousands of virtual users, this produces a much smoother, more realistic distribution of request timing than everyone pausing for the exact same duration.

Setting a Global Pause Policy

Rather than tuning every single pause call by hand, you can set a default pause type for the whole simulation, which changes how Gatling interprets the numbers you give it.

setUp(
    scn.injectOpen(rampUsers(100).during(Duration.ofMinutes(5)))
).protocols(httpProtocol)
 .pauses(exponentialPauses());

exponentialPauses shapes pause durations around an exponential distribution centered on whatever value you pass to pause, which tends to model real human think time more naturally than a flat uniform range, since most people pause for a moderate amount of time and a smaller number pause for much longer, rather than everyone being equally likely to pause for any duration in a fixed window.

Modeling Multiple User Paths

Real traffic is not one single journey repeated by everyone. Some visitors browse and leave. Some search directly for a specific item. Some are returning customers who go straight to their order history. Gatling lets you weight different scenarios against each other to reflect this mix.

ScenarioBuilder casualBrowser = scenario("Casual Browser")
    .exec(http("Home Page").get("/"))
    .pause(3, 6)
    .exec(http("Browse Category").get("/category/electronics"))
    .pause(2, 5)
    .exec(http("View Product").get("/products/42"));

ScenarioBuilder directSearcher = scenario("Direct Searcher")
    .exec(http("Search").get("/search?q=wireless+mouse"))
    .pause(1, 3)
    .exec(http("View Product").get("/products/42"));

ScenarioBuilder returningCustomer = scenario("Returning Customer")
    .exec(http("Login").post("/auth/login"))
    .pause(1, 2)
    .exec(http("Order History").get("/account/orders"));

setUp(
    casualBrowser.injectOpen(rampUsers(60).during(Duration.ofMinutes(5))),
    directSearcher.injectOpen(rampUsers(30).during(Duration.ofMinutes(5))),
    returningCustomer.injectOpen(rampUsers(10).during(Duration.ofMinutes(5)))
).protocols(httpProtocol);

Running all three scenarios in the same setUp call, with different proportions of users, produces a much more realistic mix of traffic hitting the application at once than a single scenario ever could. Sixty percent casual browsing, thirty percent direct search, ten percent returning customers checking their orders, roughly matching whatever your actual analytics tell you about how people use the site.

Randomizing Choices Within a Scenario

Within a single scenario, you can also randomize which path a virtual user takes at a given step, using randomSwitch to weight different branches.

ScenarioBuilder browse = scenario("Browse")
    .exec(http("Home Page").get("/"))
    .pause(2, 4)
    .randomSwitch().on(
        Choice.withWeight(70.0, exec(http("Browse Electronics").get("/category/electronics"))),
        Choice.withWeight(20.0, exec(http("Browse Clothing").get("/category/clothing"))),
        Choice.withWeight(10.0, exec(http("Browse Books").get("/category/books")))
    );

Seventy percent of virtual users following this scenario browse electronics, twenty percent browse clothing, and ten percent browse books, all within the same scenario definition, which keeps related traffic grouped together logically while still reflecting a realistic split in behavior.

Wrapping Up

A load test is only as useful as how closely it resembles real traffic. Randomized pauses instead of fixed ones, multiple weighted scenarios instead of a single repeated path, and randomized branching within a scenario all push your test closer to something that reflects actual user behavior rather than a mechanical script.

Next time, we look at the different broad categories of performance testing, load, stress, soak, and spike, and how to configure each of them properly using everything we have covered so far.