skip to content

In Gatling, which check types can return more than one occurrence, and what happens when you call findAll or count on one that cannot?

level: middleimportance: should knowfreq 46%

answer

  1. Two builder types, not one
  2. MultipleFind adds find(n), findAll, count
  3. jsonPath multiple, jmesPath single
  4. The restriction is a compile error
  5. substring extracts indices, not text

basics

~20 s

Multi-occurrence check types such as regex, css, xpath, jsonPath, substring and header return a MultipleFind builder that offers find(n), findAll, findRandom and count. Single-value types such as status, jmesPath, currentLocation, responseTimeInMillis and bodyString do not, so those calls fail to compile.

solid answer

~40 s

Gatling splits check builders into two types. A **`MultipleFind`** adds `find(n)`, `findAll()`, `findRandom()` and `count()` on top of the single-value `Find`. `regex`, `css`, `xpath`, `jsonPath`, `substring`, `header` and `headerRegex` are `MultipleFind`; `status`, `currentLocation`, `jmesPath`, `responseTimeInMillis`, `bodyString`, `bodyBytes`, `bodyLength` and `bodyStream` are plain `Find`. The pairing that catches people is **`jsonPath` versus `jmesPath`**: they look interchangeable, but only `jsonPath` can return several values, so `jmesPath("foo").findAll()` does not exist. Because the restriction lives in the **type**, the failure is a compile error, not a run-time surprise. Also note `substring` extracts **indices**, not matched text, so `substring("foo").count()` counts occurrences while `findAll()` yields a list of Ints.

code

java · 21 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 OccurrenceSimulation extends Simulation {

  ScenarioBuilder scn = scenario("Orders")
    .exec(http("List orders")
      .get("/orders")
      // jsonPath is a MultipleFind: all of these compile
      .check(jsonPath("$..orderId").findAll().saveAs("orderIds"))
      .check(jsonPath("$..orderId").count().gt(0))
      .check(jsonPath("$..orderId").find(1).saveAs("secondOrderId"))
      // jmesPath is a plain Find: only the single-value form exists
      .check(jmesPath("total").ofInt().gte(1)));

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

go deeper

for a junior

Be ready to name the extraction options: find, find(n), findAll, findRandom and count, and to say that find(n) counts from zero.

for a middle

Explain that Find and MultipleFind are two builder types, so calling findAll on a single-value check is a compile error rather than a run-time failure.

for a senior

Show why you would choose jmesPath for a single value and jsonPath where several nodes may match, using the type as the signal.

for a principal

Own the house guidance on substring versus regex for presence checks, given substring's lower cost and its index-valued result.

The extraction step is the second rung of Gatling's check chain, and which rungs are even offered to you is decided by the **type** the check builder returns — not by the response you happen to get back. ## Two builder types, one hierarchy * **`Find<X>`** — a single value. It offers the parameterless `find()` and goes straight on to validation. * **`MultipleFind<X> extends Find<X>`** — adds `find(int occurrence)`, `findAll()`, `findRandom()`, `findRandom(num)`, `findRandom(num, failIfLess)` and `count()`. Because `MultipleFind` extends `Find`, everything a single-value check can do a multi-value check can do too. The reverse is not true, and that asymmetry is the whole answer. ## Which check type is which | check type | builder | notes | |---|---|---| | `regex`, `css`, `xpath`, `substring` | `MultipleFind` | body extractors, many matches expected | | `jsonPath`, `jsonpJsonPath` | `MultipleFind` | a JSONPath expression can select several nodes | | `jmesPath`, `jsonpJmesPath` | **`Find`** | JMESPath returns exactly one result by design | | `header(name)`, `headerRegex(name, pattern)` | `MultipleFind` | a header may legitimately repeat | | `status`, `currentLocation` | `Find` | one status, one final URL | | `responseTimeInMillis`, `bodyString`, `bodyBytes`, `bodyLength`, `bodyStream` | `Find` | one value per response | | `form(selector)` | `MultipleFind` | captures inputs into a Map | ## What actually happens when you call the wrong one Nothing at run time — the code does not compile. `jmesPath("foo")` hands back a `Find`-shaped builder in the Java API and a `CheckBuilder.Find` in Scala, and neither declares `findAll` or `count`. Gatling's own sample makes the point directly: it notes that `jmesPath("foo")` and `jmesPath("foo").find()` are identical *"because jmesPath can only return 1 value, so find is better omitted"*, while `jsonPath("$.foo")`, `jsonPath("$.foo").find()` and `jsonPath("$.foo").find(0)` are three spellings of the same single-value check on a builder that **could** have returned more. This is the practical reason to prefer `jmesPath` when you want one value and `jsonPath` when you may want several: the type tells you which you have. ## The extraction options, precisely 1. **`find()`** — first or only occurrence; identical to `find(0)`. This is what an omitted extraction step becomes. 2. **`find(n)`** — a **zero-based** rank, so `find(1)` is the second occurrence. 3. **`findAll()`** — every occurrence, as a list. The validation step then compares lists, so `is(...)` takes a list. 4. **`findRandom()`** — one at random; `findRandom(num)` takes several, and an optional `failIfLess` boolean (default `false`) decides whether taking fewer than asked is a failure. 5. **`count()`** — the number of occurrences, as an Int. The validation step then compares numbers: `regex("https://(.*)").count().is(5)`. ## `substring` extracts indices, not text `substring(pattern)` is a `MultipleFind` whose extracted type is **Int** — it reports the *positions* of the occurrences in the body, not the matched strings. That is why Gatling's own sample comments `substring("foo").findAll().saveAs("indices")` as saving a list of Ints. It is a cheap presence test, deliberately cheaper than a regular expression, and it is not a way to pull text out of a body. Use `regex` or `jsonPath` for that. ## How the extraction step shows up in a failure Gatling names an unnamed check `extractorName.arity.validatorName`, where the **arity** is exactly the extraction step you chose. It renders as `find`, `find(2)`, `findAll`, `count`, `findRandom` or `findRandom(3, true)`. Note that `find(0)` renders as plain `find`, so an explicit first-occurrence extraction is indistinguishable in the message from an omitted one. ## Typed extraction is a separate step Asking for several occurrences is not the same as asking for a different *type*. Changing the extracted type is its own step — `ofInt()`, `ofNode()` and friends in the Java-family SDKs, `ofType[T]` in Scala — and it composes with the extraction step rather than replacing it.

  • Why does `jmesPath("foo").findAll()` not compile while `jsonPath("$.foo").findAll()` does?
    `jsonPath` returns a `MultipleFind` builder, which declares `findAll`, `find(n)`, `findRandom` and `count`. `jmesPath` returns a plain `Find`, because a JMESPath expression yields exactly one result. The restriction is in the returned type, so the compiler rejects it before the run starts.
  • What does `findRandom(3, true)` do differently from `findRandom(3)`?
    Both pick up to three occurrences at random. The optional second argument is `failIfLess`, which defaults to `false`: left off, the check succeeds with however many it found; set to `true`, the check fails when fewer than three matched.
  • If `findAll()` is used, what does the validation step compare?
    A list. The extracted type becomes a list of the check's element type, so `is(...)` expects a list and `saveAs` stores one. Gatling's own sample asserts `regex("https://(.*)/.*").findAll().is(List.of("www.google.com", "gatling.io"))`.

saying these in an interview costs you the question

  • Treating jsonPath and jmesPath as interchangeable in every position
  • Expecting findAll on status or responseTimeInMillis to compile
  • Thinking substring extracts the matched text rather than indices
  • Reading find(1) as the first occurrence instead of the second