skip to content

In a Gatling suite, a response field is present on only some responses — how would you decide between a plain check, checkIf, optional() and withDefault for it?

level: principalimportance: should knowfreq 32%

answer

  1. Four constructs, four different meanings
  2. Ask what absence actually means
  3. checkIf when the condition is knowable
  4. optional never fails, never saves
  5. Optional everywhere hides real breaks

basics

~20 s

Decide by what absence means. If absence is a defect, leave the implicit exists() in place. If you know in advance when the field should be there, gate the check with checkIf. If absence is legitimate, use optional() to capture without failing, or withDefault to substitute a value.

solid answer

~50 s

Gatling gives you four distinct answers and they are not interchangeable. A **plain check** inherits the implicit `exists()`, so absence is a KO — the right choice when a missing field really is a defect. **`checkIf(condition).then(checks)`** runs the check only when a Session condition or an EL boolean holds, so you assert exactly where the contract says the field must appear. **`optional()`** replaces the validation step: it never fails, and when nothing matched the remaining steps including `saveAs` do not fire, so an existing Session value is neither replaced nor removed. **`withDefault(value)`** is a transform step that supplies a fallback so the later steps still run. Choosing `optional()` everywhere is the failure mode: it converts a real contract break into a silent pass, and the damage shows up later as an unresolvable placeholder on a different request.

code

java · 23 lines
java
import io.gatling.javaapi.core.*;
import io.gatling.javaapi.http.*;
import static io.gatling.javaapi.core.CoreDsl.*;
import static io.gatling.javaapi.http.HttpDsl.*;

public class OptionalFieldSimulation extends Simulation {

  ScenarioBuilder scn = scenario("Checkout")
    .exec(http("Place order")
      .post("/orders")
      .check(status().is(201))
      // required: absence is a defect, keep the implicit exists()
      .check(jsonPath("$.orderId").saveAs("orderId"))
      // known in advance: only members carry a loyalty id
      .checkIf("#{isMember}").then(
        jsonPath("$.loyaltyId").saveAs("loyaltyId"))
      // legitimately absent: never fails, and never overwrites
      .check(jsonPath("$.couponCode").optional().saveAs("couponCode")));

  {
    setUp(scn.injectOpen(atOnceUsers(1)));
  }
}

go deeper

for a junior

Be ready to name the four constructs: a plain check, checkIf, optional and withDefault, and to say that a plain check already requires the value to exist.

for a middle

Explain that optional never fails and never saves, so a value already under that key survives the response that did not carry one.

for a senior

Show how making every capture optional moves a failure away from the request that actually broke the contract.

for a principal

Own the suite convention: correlated values stay required, branch-dependent fields use checkIf, and optional is a documented exception.

"The field is not always there" is the point at which a Gatling suite either keeps its diagnostic value or quietly loses it. The check pipeline offers four different answers, and the decision is not a style preference — each one moves the failure to a different place. ## The four options | construct | where it sits in the chain | what absence does | |---|---|---| | plain check | implicit `exists()` validation | check fails, request KO, nothing saved | | `checkIf(cond).then(...)` | wraps the whole check | check does not run at all when the condition is false | | `optional()` | replaces the validation step | check passes; later steps, `saveAs` included, do not fire | | `withDefault(v)` | a transform step | a fallback value flows on to validation and saving | In Scala `checkIf` takes a block — `checkIf(cond) { checks }` — while the Java API, Kotlin and the JavaScript/TypeScript SDK spell it `checkIf(cond).then(checks)`. Both accept an EL String that resolves to a Boolean, or a function over the Session. ## The decision, in order 1. **Ask what absence means.** If the service contract says the field is always present, absence is a defect and the plain check is correct — do nothing. The implicit `exists()` is already the right behaviour, and reaching for `optional()` here is the single most common way a suite stops finding bugs. 2. **Ask whether you know in advance.** If your scenario knows which branch it is on — a guest checkout has no loyalty number, a first page has no cursor — then the condition is knowable and `checkIf` is the honest expression of it. It asserts the field where the contract requires it and does not look for it where it does not. That is strictly stronger than making the check optional everywhere, because the positive case still fails when it should. 3. **Only then reach for `optional()`.** Use it when absence is genuinely legitimate and unpredictable from the Session. Be aware of the exact semantics: nothing matched means the later steps do not run, so any value already stored under that `saveAs` key survives untouched — it is neither overwritten nor cleared. In a looping scenario that is a real hazard: iteration two can send iteration one's value without anything going red. 4. **Use `withDefault` when downstream needs a value.** If a later request must send *something*, substituting a known sentinel keeps the pipeline honest and makes the substitution visible in the traffic, rather than leaving a stale Session value in place. ## Why "optional everywhere" fails A capture in Gatling is also an assertion, by design. Strip the assertion off every capture and you have moved the failure from the request that broke the contract to some later request that could not resolve a placeholder — or, worse, to no request at all, because a stale value from a previous iteration was still there. The report then blames the wrong step, and a genuine regression reads as a scenario bug. ## What to standardise across a suite * **Correlated values keep their implicit `exists()`.** Anything a later request depends on should fail loudly at the point of capture. That is the whole argument for Gatling's default. * **Branch-dependent fields use `checkIf`, not `optional()`.** If the scenario can express the condition, express it. * **`optional()` is a documented exception, not a default.** Where it is used, say in the check's `name(...)` that absence is expected, so the next reader does not have to infer it. * **Never use `optional()` to suppress a flaky check.** A check that fails intermittently is either a real defect or a wrong expectation; making it optional records neither. ## The observability side of the same decision Whichever branch you take, the error surface is part of the decision. An unnamed check is reported as `extractorName.arity.validatorName` — `jsonPath($.loyaltyId).find.exists` — which tells a reader what the check did but not what it meant. `name(...)` fixes that with a static String. And where a check **compares** against an expected value that varies per user, `logActualValueInError(false)`, added in Gatling **3.15**, keeps one failure mode from becoming one error row per virtual user in the report. It has nothing to suppress on the capture checks in this decision — their implicit `exists()` never reads the flag and can only report the constant `found nothing` — so there, `name(...)` is the whole of the error-surface improvement. ## Where this decision is not yours to make Deciding *which* properties a run should assert at all, and what a performance pass rule should be, sits outside the check pipeline. This decision is narrower and concrete: given that you have decided to look at a field, which of Gatling's four constructs expresses what its absence means. Answer that one honestly and the report stays diagnostic.

  • What exactly happens to a Session key when an `optional()` check matches nothing?
    Nothing. The validation passes, but the extraction produced no value, so the saving step does not run. Any value already stored under that key is neither replaced nor removed. In a loop that means iteration two can still be carrying iteration one's value, with no failure anywhere.
  • How does `checkIf` differ between the Gatling SDKs?
    In the Java API, Kotlin and the JavaScript/TypeScript SDK it is `checkIf(condition).then(checks)`. In Scala it takes a block: `checkIf(condition) { checks }`. Both forms accept an EL String resolving to a Boolean, or a function over the Session.
  • Why not just drop the check and let the later request fail on the missing placeholder?
    Because the failure then names the wrong request. The report blames the step that could not resolve the value rather than the response that never carried it, and a stale value from a previous iteration can hide the break entirely.

saying these in an interview costs you the question

  • Making every capture optional to keep a run green
  • Using optional() to suppress an intermittently failing check
  • Assuming optional() clears a stale value under the same key
  • Reaching for optional() when the branch condition is already known