skip to content

Assertion Chain Grammar

The four-stage assertion chain registered on setUp - scope, statistic, metric, condition - and how Scala spells it differently from Java, Kotlin and JavaScript, which is the trap.

on this pageshow

explore

questions

6

In a Gatling simulation, where do you register an assertions chain, and what does each link of that chain select?

level: juniorimportance: must knowfreq 72%

answer

  1. Registered on setUp, judged afterwards
  2. Scope, statistic, metric, condition
  3. assertions(...) takes as many as you like
  4. All of them ANDed; one failure is enough
  5. details("Checkout").responseTime().percentile(95.0).lt(800)

basics

~10 s

You register them with assertions(...) on the setUp call, in the Simulation constructor. Each assertion chains a scope, then a statistic, then a metric, then a condition - for example details("Checkout").responseTime().percentile(95.0).lt(800).

solid answer

~30 s

Assertions are pass/fail rules over the finished run's global statistics, registered by calling `assertions(...)` on what `setUp(...)` returns, inside the `Simulation` constructor. Each one chains a **scope** (`global`, `forAll` or `details(path)`), a **statistic** (`responseTime`, `allRequests`, `failedRequests`, `successfulRequests` or `requestsPerSec`), a **metric** that reduces it to one number (`max`, `mean`, `percentile(v)`, `count`, `percent`...), and a **condition** (`lt`, `gte`, `between`, `is`...). `assertions(...)` takes as many as you like and they are ANDed: one failure fails the run. `requestsPerSec` is the exception — it carries no metric link and goes straight to the condition.

code

java · 15 lines
java
import io.gatling.javaapi.core.*;

import static io.gatling.javaapi.core.CoreDsl.*;

public class CheckoutGate extends Simulation {
  public CheckoutGate() {
    ScenarioBuilder scn = scenario("checkout");

    setUp(scn.injectOpen(atOnceUsers(1)))
      .assertions(
        details("Checkout").responseTime().percentile(95.0).lt(800),
        global().failedRequests().count().lt(10L)
      );
  }
}

go deeper

for a junior

Be ready to name the four links in order and to write one complete assertion from memory, including where the assertions call attaches.

for a middle

Be ready to explain why the compiler rejects illegal statistic and metric pairings, and why requestsPerSec has one link fewer than the others.

for a senior

Be ready to say what an assertion can and cannot do about a run in progress, and where the verdict and its actual value are reported afterwards.

for a principal

Be ready to argue what belongs in a simulation's assertion block versus in the pipeline step that runs it, given that every rule is ANDed into a single verdict.

## What a Gatling assertion is A Gatling **assertion** is a pass/fail rule over the **global statistics** of a finished run. It is not a per-response check: nothing in an assertion looks at an individual reply while the run is in flight. You declare the rules up front, Gatling runs the simulation, and only afterwards does it compute the run's statistics and test your rules against them. Assertions are registered by calling `assertions(...)` on the object that `setUp(...)` returns, inside the `Simulation` constructor: ```java setUp(scn.injectOpen(atOnceUsers(1))) .assertions( details("Checkout").responseTime().percentile(95.0).lt(800), global().failedRequests().count().lt(10L) ); ``` `assertions(...)` is variadic and takes as many assertions as you like. They are **combined with AND**: every one must hold, and the run fails if even one does not. ## The chain, link by link Each individual assertion is built by chaining four kinds of link, always in this order: | link | what it selects | the values | |---|---|---| | **scope** | which requests the statistic is computed from | `global`, `forAll`, `details(path)` | | **statistic** | which quantity is being judged | `responseTime`, `allRequests`, `failedRequests`, `successfulRequests`, `requestsPerSec` | | **metric** | how that quantity becomes one number | `min`, `max`, `mean`, `stdDev`, `percentile1`–`percentile4`, `percentile(v)` for response time; `count`, `percent` for the three count statistics | | **condition** | the test applied to that number | `lt`, `lte`, `gt`, `gte`, `between`, `around`, `deviatesAround`, `is`, `in` | ## The ladder is type-fixed, and one statistic is shorter Each link returns a different builder type, so the compiler enforces both the order and the legal combinations. You cannot reach a response-time reducer from a count statistic or the other way round: - `responseTime()` yields a time-metric stage, so only the time reducers are reachable. `global().responseTime().percent()` does not compile. - `allRequests()`, `failedRequests()` and `successfulRequests()` yield a count-metric stage that exposes exactly two methods, `count()` and `percent()`. `global().failedRequests().max()` does not compile. - `requestsPerSec()` **skips the metric link altogether** and goes straight to the condition, because there is only one number to reduce to — the mean rate over the run. `details("MyGroup").requestsPerSec().between(100.0, 1000.0)` is a complete assertion with three links. That last bullet is worth holding on to. Gatling's reference describes the chain as four steps without qualification, but `requestsPerSec` is genuinely a three-step chain, and looking for a `mean()` after it is the commonest way to get stuck. ## When they are evaluated Nothing is judged while users are running. After the simulation stops, Gatling parses the run's own log data, computes the statistics, and then validates every registered assertion against them. Each assertion prints its own line on the console with its verdict and the actual value it saw, and the generated HTML report gains an **Assertions** table with one OK/KO row per rule. Declaring assertions forces that log to be parsed even when report generation is switched off, because the verdict cannot be produced any other way. Two consequences follow directly. First, an assertion can never stop a run early — by the time it is evaluated there is nothing left to stop. Second, an assertion whose scope resolves to no data is a **failure**, not a silent pass: a `details(...)` path naming a request that never ran cannot be resolved, and an unresolvable assertion counts against the run. ## The commonest ways the chain refuses to compile Three mistakes account for most of them, and all three are the type system doing its job: 1. Reaching for a metric the statistic does not own — `percent()` after `responseTime()`, or `mean()` after `failedRequests()`. 2. Looking for a metric link after `requestsPerSec()`, which has none. 3. In Java and Kotlin, handing the wrong numeric literal to the condition. `count()` is typed on `Long` and `percent()` and `requestsPerSec()` on `Double`, so `count().lt(10)` and `percent().gt(95)` are rejected; they need `10L` and `95.0`. ## The spelling differs between the SDKs The link names are the same everywhere, but the surface is not: 1. **Scala** writes the intermediate links as parameterless members, without parentheses — `global.responseTime.max.lt(50)` — and builds a nested path with a `/` operator, `details("G" / "R")`. 2. **Java, Kotlin and the JavaScript/TypeScript SDK** write every link as a method call — `global().responseTime().max().lt(50)` — and pass a nested path as separate arguments, `details("G", "R")`. 3. The argument types bite in Java and Kotlin: `percent()` and `requestsPerSec()` are typed on `Double` and `count()` on `Long`, so `percent().gt(95.0)` and `count().lt(10L)` are required where Scala's numeric widening accepts a plain `95` or `10`. Never describe one of these as "the DSL" without naming the language — a sentence about `global.responseTime` is wrong in four of the five, and one about `global()` is wrong in Scala.

  • Why does `global().failedRequests().max()` fail to compile?
    Because each link returns a different builder type. `failedRequests()` yields the count-metric stage, which exposes exactly `count()` and `percent()`. `max()` lives only on the time-metric stage that `responseTime()` returns. The ladder is enforced by types, so illegal statistic/metric pairings are rejected at compile time rather than at run time.
  • Can an assertion abort a Gatling run once it is clearly going to fail?
    No. Assertions are evaluated only after the simulation has stopped, from the run's recorded statistics - by then there is nothing left to abort. If you need a running simulation to stop itself, that is a scenario-level concern, not an assertion. Assertions decide the verdict, never the run's length.

It reads like a sentence with a fixed word order: where to look, what to measure, how to boil it down to one number, and what that number must be. Get the order wrong and the compiler will not let you finish the sentence.

saying these in an interview costs you the question

  • Thinking an assertion can stop a run while it is still going
  • Calling percent() on responseTime, or max() on failedRequests
  • Expecting a mean() link after requestsPerSec
  • Believing a second assertion overrides rather than adds to the first
open as a page

In a Gatling assertion, what does the `percentile3` response-time metric measure, and what can change its meaning without the simulation code changing?

level: middleimportance: should knowfreq 44%

basics

~20 s

It asserts on the third configured percentile, which defaults to the 95th but is read from gatling.charting.indicators.percentile3. Changing that key, or overriding it with a system property, silently repoints the same line of code at a different percentile.

open as a page

In Gatling's assertion DSL, what do `global`, `forAll` and `details(...)` each compute over, and how do you address a request that runs inside a group?

level: middleimportance: should knowfreq 52%

basics

~20 s

global pools every request; forAll applies the rule to each request type separately; details(path) targets one named request or group. A request inside a group needs its full path - details("Purchase", "Checkout") in Java, details("Purchase" / "Checkout") in Scala.

open as a page

In a Gatling assertion, how do the `count` and `percent` metrics on `failedRequests` differ, and what is each one measured against?

level: seniorimportance: should knowfreq 46%

basics

~20 s

count() is the absolute number of failures in the scope; percent() is that number as a share of all requests in the same scope. An absolute ceiling gets more permissive as a run grows longer, faster or spreads across machines; a percentage does not.

open as a page

Across a Gatling suite that gates CI, would you write the response-time budget as a `global` assertion, a `forAll` assertion, or one `details(...)` assertion per critical request, and how would you decide?

level: principalimportance: should knowfreq 38%

basics

~20 s

Usually all three, layered: details rules on the journeys that matter, a loose forAll safety net, and a global volume floor. global alone aggregates away the request you care about; forAll alone binds every request to one budget; details alone breaks on renames.

open as a page

In a Gatling simulation, what happens when `.assertions(...)` is called twice on the same `setUp`, and does the language change the answer?

level: seniorimportance: nice to knowfreq 26%

basics

~20 s

In Scala the second call appends to the first. In the Java API, which Kotlin also uses, it replaces the first list outright, so the earlier rules are silently dropped. Call assertions once and pass every rule to it.

open as a page