Assertions: .expect() and .check()¶
.expect() and .check() validate response properties using scopes --- named checks against parts of the response like status code, timing, body, and headers.
The only difference between them: .expect() failures are hard (execution stops), .check() failures are soft (recorded, execution continues).
Complete Evaluation¶
Both methods evaluate all their scopes before any failure takes effect. If .expect() has three scopes and two fail, both failures are recorded together --- you see every problem at once, not just the first.
// If both status and timing fail, you see both failures in the result
.expect(
status: 200,
totalDelayMs: 500
)
Scope Forms¶
Every scope accepts three forms.
Shorthand --- value only¶
The operator is inferred from the scope name (see Default Operators):
For status, you can pass an array --- any match passes:
Value + operator¶
Override the default operator:
Full form --- with options¶
Includes the options block, which is a placeholder for extensions:
.expect(
status: {
value: 200,
op: "eq",
options: {
// Extension fields go here (e.g. notification templates)
}
}
)
All three forms produce the same core behaviour. The full form exists so extensions can attach metadata to individual scopes.
Available Scopes¶
| Scope | Type | Description |
|---|---|---|
status |
integer or integer[] | HTTP status code. Array means "any of these". |
body |
body match | Body content or schema validation. See Body Matching. |
headers |
object | Each key-value pair must match. Keys are case-insensitive. |
bodySize |
size string | Body size threshold (e.g. "50kb", "10m"). Also gates body capture. |
totalDelayMs |
integer | Total response time threshold in ms. |
dns |
integer | DNS resolution time threshold in ms. |
connect |
integer | TCP connection time threshold in ms. |
tls |
integer | TLS handshake time threshold in ms. Skipped when TLS time is 0. |
ttfb |
integer | Time to first byte threshold in ms. |
transfer |
integer | Body transfer time threshold in ms. |
size |
integer | Exact response body size in bytes. |
redirects |
string | URL match against redirect chain. See Redirects Scope. |
Size string format for bodySize: a number optionally followed by a unit --- 50, 50k, 50kb, 10m, 10mb, 1g, 1gb.
Default Operators¶
When you omit op, each scope uses a natural default:
| Scope | Default op | Why |
|---|---|---|
status |
eq |
Status codes are exact matches |
body |
eq |
Exact or schema match |
headers |
eq |
Each header matched exactly |
size |
eq |
Exact byte count |
redirects |
eq |
URL equality |
totalDelayMs |
lt |
"Must be faster than this" |
dns |
lt |
Timing threshold |
connect |
lt |
Timing threshold |
tls |
lt |
Timing threshold |
ttfb |
lt |
Timing threshold |
transfer |
lt |
Timing threshold |
bodySize |
lt |
Size threshold |
The available operators are: lt, lte, eq, neq, gte, gt.
Body Matching¶
The body scope supports three kinds of matching:
Schema validation¶
Validates the response JSON against a JSON Schema stored in a script variable:
The variable must contain a valid JSON Schema document. If the variable is null, this is a hard fail.
Schema match modes control how extra fields are handled:
// Loose (default): schema fields must be present and valid, extra fields allowed
.expect(body: schema($user_schema))
.expect(body: { value: schema($user_schema), mode: "loose" })
// Strict: no extra fields allowed at any level
.expect(body: { value: schema($user_schema), mode: "strict" })
| Mode | Behaviour |
|---|---|
loose (default) |
All schema fields must be present and valid. Additional fields are ignored. |
strict |
All schema fields must be present and valid. Any extra field at any level fails. |
When a schema check fails, the assertion record includes the first validation error path and message (e.g. { path: ".user.id", detail: "expected integer, got string" }).
Literal string¶
The raw response body must match the string exactly:
Variable match¶
The raw response body must equal the runtime value of the variable:
Note
The mode field only applies to schema(...) body matching. Literal string and variable matches are always byte-exact; mode on those is silently ignored.
Redirects Scope¶
The redirects scope asserts against the chain of redirect URLs followed during the call. It uses a match field to select which redirect to compare:
// Shorthand --- defaults to match: "any"
.expect(redirects: "/login")
// Full form --- explicit match type
.expect(redirects: { value: "/login", match: "first" })
.expect(redirects: { value: "/final", match: "last" })
.expect(redirects: { value: "/somewhere", match: "any" })
match |
Behaviour |
|---|---|
first |
Compare against the first redirect hop. Fails if no redirects occurred. |
last |
Compare against the last redirect hop. Fails if no redirects occurred. |
any (default) |
Pass if the value matches any redirect in the chain. |
Combining .expect() and .check()¶
A common pattern: use .expect() for must-not-break thresholds and .check() for performance goals: