Skip to content

06 — Checks and Assertions

Checks validate HTTP responses and optionally extract values into the session. Assertions define pass/fail criteria for the whole simulation (used in CI).


Anatomy of a check

check( <extractor> . <condition> )
check( <extractor> . <condition> . saveAs("sessionKey") )

Status code checks

.check(status().is(200))
.check(status().not(404))
.check(status().in(200, 201, 204))

Response body checks

Substring / regex

.check(bodyString().is("OK"))
.check(substring("success").exists())         // check body contains substring
.check(bodyBytes().is("hello".getBytes()))

// Regex — first capture group
.check(regex("\"id\":(\\d+)").is("42"))
.check(regex("\"id\":(\\d+)").saveAs("userId"))

// Regex — all occurrences
.check(regex("\"tag\":\"(\\w+)\"").findAll().saveAs("tags"))

// Regex — count
.check(regex("\"item\"").count().is(5))

JSON path

Requires com.jayway.jsonpath:json-path (included transitively).

.check(jsonPath("$.status").is("active"))
.check(jsonPath("$.user.id").saveAs("userId"))
.check(jsonPath("$.items[0].name").is("Widget"))
.check(jsonPath("$.items.length()").ofInt().gt(0))
.check(jsonPath("$.items[*].id").findAll().saveAs("itemIds"))

Type conversions:

.check(jsonPath("$.count").ofInt().gte(1))
.check(jsonPath("$.price").ofDouble().lt(100.0))
.check(jsonPath("$.active").ofBoolean().is(true))

JMESPath (alternative JSON)

.check(jmesPath("items[0].name").is("Widget"))
.check(jmesPath("length(items)").ofInt().gte(1))

XPath (for XML / SOAP)

.check(xpath("//status/text()").is("OK"))
.check(xpath("//user/@id").saveAs("userId"))

CSS selector

.check(css("h1").is("Welcome"))
.check(css("input[name=csrf]", "value").saveAs("csrfToken"))

Header checks

.check(header("Content-Type").is("application/json"))
.check(header("Location").saveAs("location"))
.check(headerRegex("Set-Cookie", "session=(.+?);").saveAs("sessionCookie"))

Response time check (per-request)

.check(responseTimeInMillis().lt(500))

Condition methods

Method Meaning
.is(value) Exact equality
.not(value) Not equal
.in(v1, v2, …) Any of the values
.gt(value) Greater than
.gte(value) Greater than or equal
.lt(value) Less than
.lte(value) Less than or equal
.exists() Value is present
.notExists() Value is absent
.isNull() Value is null
.notNull() Value is not null

For substring matching, use the substring("text") check type instead of a condition method: .check(substring("success").exists())


Saving extracted values

Use .saveAs("key") after any condition (or instead of a condition):

.check(jsonPath("$.token").saveAs("authToken"))
.check(jsonPath("$.userId").ofInt().saveAs("userId"))
.check(header("Location").saveAs("resourceUrl"))
.check(regex("orderId=(\\w+)").saveAs("orderId"))

The saved value is then available in the session as #{authToken}, #{userId}, etc.


Optional vs mandatory checks

By default a check is mandatory — failure marks the request as KO. Use .optional() to make it soft (missing value doesn't fail):

.check(jsonPath("$.debugInfo").optional().saveAs("debug"))

Multiple checks on one request

http("Login").post("/login")
    .body(StringBody("""{"username":"#{username}","password":"#{password}"}"""))
    .asJson()
    .check(status().is(200))
    .check(jsonPath("$.token").saveAs("authToken"))
    .check(jsonPath("$.expiresIn").ofInt().gt(0))

Global simulation assertions

Placed in the setUp block — not on individual requests:

setUp(...)
    .assertions(
        // All requests
        global().responseTime().mean().lt(200),
        global().responseTime().percentile(95).lt(500),
        global().responseTime().max().lt(2000),
        global().successfulRequests().percent().gt(99.0),
        global().failedRequests().count().lt(10L),

        // Specific request by name
        details("Login").responseTime().percentile(99).lt(1000),
        details("Login").failedRequests().percent().lt(1.0),

        // Specific group
        details("Checkout flow").responseTime().percentile(95).lt(800)
    );

Assertion failure causes a non-zero exit code — perfect for CI gates.


Kotlin note: is is a reserved keyword

In Kotlin, is is reserved. You have two options:

// Option 1: backticks
.check(status().`is`(200))

// Option 2: alias (preferred for readability)
.check(status().shouldBe(200))

The shouldBe alias works for all check validators where is would be used.


Full example

Java

ScenarioBuilder scn = scenario("Auth flow")
    .exec(
        http("Login").post("/auth/login")
            .body(StringBody("""{"username":"#{username}","password":"#{password}"}"""))
            .asJson()
            .check(status().is(200))
            .check(jsonPath("$.accessToken").saveAs("accessToken"))
            .check(jsonPath("$.userId").ofInt().saveAs("userId"))
    )
    .exec(
        http("Get profile").get("/users/#{userId}")
            .header("Authorization", "Bearer #{accessToken}")
            .check(status().is(200))
            .check(jsonPath("$.email").exists())
    );

Kotlin

val scn = scenario("Auth flow")
    .exec(
        http("Login").post("/auth/login")
            .body(StringBody("""{"username":"#{username}","password":"#{password}"}"""))
            .asJson()
            .check(status().`is`(200))
            .check(jsonPath("$.accessToken").saveAs("accessToken"))
            .check(jsonPath("$.userId").ofInt().saveAs("userId"))
    )
    .exec(
        http("Get profile").get("/users/#{userId}")
            .header("Authorization", "Bearer #{accessToken}")
            .check(status().`is`(200))
            .check(jsonPath("$.email").exists())
    )