In a Gatling simulation, what happens when `.assertions(...)` is called twice on the same `setUp`, and does the language change the answer?
answer
- Simulation is the one mutable DSL object
- Scala concatenates, the Java API assigns
- Kotlin inherits the Java API behaviour
- Lost rules make a run pass, not fail
- protocols(...) diverges the same way; throttle does not
basics
~20 sIn 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.
solid answer
~50 sIt depends on the binding layer, and the difference is silent. `Simulation` is the one deliberately mutable DSL object: it declares the field of registered assertions, and the `SetUp` that `setUp(...)` returns is a fieldless inner class that only writes into its enclosing simulation. Scala's implementation concatenates into that field, so a second `.assertions(...)` adds to the first. The Java API assigns to it, so a second call **discards** everything the first registered - and Kotlin rides on the Java API, so it behaves the same way. Nothing warns, and because losing rules makes a run easier to pass, the symptom is a pipeline that goes green rather than red. The safe shape everywhere is one `.assertions(...)` call taking every rule, building the list in a local variable first if the rules are assembled conditionally.
code
java · 17 linesimport java.util.*;
import io.gatling.javaapi.core.*;
import static io.gatling.javaapi.core.CoreDsl.*;
public class SafeGate extends Simulation {
public SafeGate() {
ScenarioBuilder scn = scenario("checkout");
List<Assertion> rules = new ArrayList<>();
rules.add(details("Checkout").responseTime().percentile(95.0).lt(800));
rules.add(global().failedRequests().count().lt(10L));
setUp(scn.injectOpen(atOnceUsers(1))).assertions(rules);
}
}go deeper
Be ready to say that assertions is normally called once with every rule passed to it, and that this is the shape the reference uses.
Be ready to explain that Simulation is the mutable object holding the assertions field, and that the second call assigns in the Java API but concatenates in Scala.
Be ready to explain why a silently dropped assertion shows up as a greener pipeline, and to name the code shapes that produce a second call.
Be ready to set a convention for how shared house rules reach a simulation, given that a base class registering its own rules is the risky shape.
## The short answer, and why it is not the same everywhere Calling `.assertions(...)` twice on the same `setUp` does two different things depending on which binding layer you are writing against: | SDK | what a second `.assertions(...)` does | |---|---| | Scala core | **appends** - the new assertions are added to the ones already registered | | Java API (and Kotlin, which sits on it) | **replaces** - the earlier list is discarded | The mechanism is unglamorous and completely decides the behaviour. Gatling's `Simulation` is the one deliberately **mutable** component in the DSL — the Java source says exactly that in its class comment — and it is `Simulation` that declares the `_assertions` field. `SetUp`, the object `setUp(...)` hands back, declares no state of its own; each `.assertions(...)` call on it writes to the enclosing simulation's field. The Scala implementation writes `_assertions = _assertions ++ asserts`, concatenating. The Java implementation writes `_assertions = assertions`, a plain assignment. Same intent, different operator, opposite outcome. ## Why this is worth knowing Nothing about the failure is loud. Both forms compile, neither warns, and the run completes normally. What you get in Java or Kotlin is a run that judged fewer rules than the file appears to declare — and because a run with nothing left to judge passes, deleting your rules by accident makes the pipeline **greener**, not redder. A gate that quietly stops gating is worse than one that fails. The shape that produces it is not exotic either. It arises whenever assertions are assembled in more than one place: - a helper method that adds a standard failure-rate rule, called after a per-simulation response-time rule has already been registered; - a base `Simulation` class that registers a house rule, with a subclass adding its own; - a conditional block that appends an extra assertion only when an environment variable is set. All three are natural ways to organise a suite, and all three lose rules silently under the Java API. ## The same divergence appears elsewhere This is not a one-off quirk of assertions. `.protocols(...)` behaves the same way — assignment in the Java API, concatenation in Scala — so a second `.protocols(...)` call also discards the first in Java and Kotlin while accumulating in Scala. Recognising the pattern is more useful than memorising the two cases: the Scala core tends to accumulate into the simulation's fields, the Java API tends to assign to them. It does not generalise to everything on `SetUp`, though. `throttle(...)` is a plain assignment in **both** SDKs, so there is no divergence there to look for. Do not reason by analogy about a method you have not checked. ## What to do instead The fix is to make the multiplicity explicit rather than relying on the accumulation behaviour: 1. **Call `.assertions(...)` exactly once** and pass every rule to that call. This is the shape Gatling's own documentation uses, it is correct in every SDK, and it removes the question entirely. 2. **Build a list first** when the rules are assembled conditionally, then pass the finished list to the single call. Both SDKs accept a collection of assertions as well as a varargs list, so a list built up in a local variable works everywhere. 3. If a base class must contribute rules, have it **return** its assertions rather than register them, and let the single call in the leaf simulation combine them. ```java List<Assertion> rules = new ArrayList<>(); rules.add(details("Checkout").responseTime().percentile(95.0).lt(800)); rules.add(global().failedRequests().count().lt(10L)); setUp(scn.injectOpen(atOnceUsers(1))).assertions(rules); ``` ## How to notice it has already happened The symptom is absence, so you have to look for it deliberately. Two cheap checks work: - **Count the verdict lines.** Gatling prints one line per evaluated assertion at the end of a run, with the verdict and the actual value it measured. If the file declares five rules and the console prints two, rules were discarded. The generated HTML report's Assertions table carries the same count. - **Make one rule fail on purpose.** Temporarily invert a threshold from an early `.assertions(...)` call. If the run still passes, that call's rules are not reaching the verdict. Neither needs tooling, and the first is worth doing once for any suite whose assertions are assembled in more than one place. ## A note on the other two languages The measured divergence above is between the Scala core and the Java API, which is what Kotlin compiles against. The JavaScript and TypeScript SDK is a separate published package whose source is not part of the Gatling repository, so treat its behaviour here as unverified rather than assuming it matches either side. The single-call discipline is correct regardless, which is another reason to adopt it: it is the only shape whose meaning you do not have to look up per SDK.
- Why is a dropped assertion harder to notice than a broken one?Because a run with fewer rules is easier to pass, and a run with no rules at all reports success. Losing assertions moves the pipeline towards green, so nothing in the build output draws attention to it. A broken assertion turns the run red and gets investigated; a missing one is simply never mentioned again.
- Does the same replace-versus-merge split apply to every setUp method?No. `.protocols(...)` behaves the same way - assignment in the Java API, concatenation in Scala - but `throttle(...)` is a plain assignment in both, so there is no divergence there. Check the method rather than reasoning by analogy; the pattern is a tendency of the two layers, not a rule that covers all of setUp.
saying these in an interview costs you the question
- Assuming assertions always accumulate across calls
- Treating Kotlin as following Scala rather than the Java API
- Expecting a warning when registered rules are discarded
- Generalising the replace behaviour to every setUp method