Assertions & Dot-Paths

Assertions are the core validation engine of Gherkio. Placed inside the expect block of any step, assertions inspect, type-check, and validate the returned HTTP response status, headers, body structure, and even embedded JWT tokens.


🏗️ The Anatomy of expect

The expect block defines a set of strict contracts that the HTTP response must satisfy. Gherkio evaluates these assertions sequentially upon request completion.

⚡ Fail-Fast Execution

Gherkio implements a strict fail-fast design.

  • Assertions are checked in the order they are written.
  • If any single assertion fails, step execution is immediately aborted, the entire scenario is marked as failed, and any subsequent steps (such as database queries or follow-up requests) are skipped.
  • This prevents cascading failures, reduces network bandwidth waste, and keeps your test reports clean and focused on the root cause.

🧭 Dot-Path Prefixes & Resolvers

Gherkio uses a declarative dot-notation system to target specific parts of the response. The prefix of the path determines which resolver is invoked:

Path PrefixTarget ComponentKey Case-SensitivityPurposeExample
statusHTTP Status CodeN/AValidates the exact status codestatus: 200
body.<path>JSON Response BodyCase-SensitiveTraverses response JSON structuresbody.user.email: email
headers.<key>HTTP Response HeadersCase-InsensitiveInspects response headersheaders.content-type: contains json
jwt.<claim>Decoded JWT payloadCase-SensitiveAutomatically decodes tokens and inspects claimsjwt.role: admin
schemaJSON Schema ContractN/AEnforces a full JSON schema contractschema: users/create
timing.durationRound-trip request timeN/AAsserts latency SLA/performancetiming.duration: lte 500ms

1. Response Body Traversal (body.<path>)

The body. prefix targets the response body payload (expected to be JSON). Gherkio traverses nested properties, list indices, and dictionary keys.

Key Syntax Features:

  • Nested Navigation: Use dots to descend into maps (e.g., body.user.profile.firstName).
  • Array Indexing: Use brackets to access specific list items (e.g., body.items[0].id).
  • Case Sensitivity: JSON keys are strictly case-sensitive. body.userId is different from body.userid.

Supported Matchers

Assertion ValueTypeExampleBehavior
Literal valueEqualitybody.role: adminExact string match
existsExistencebody.email: existsField must be present (any value)
not existsAbsencebody.error: not existsField must be absent
uuidFormatbody.id: uuidValid UUID v4 format
emailFormatbody.email: emailValid email format
datetimeFormatbody.createdAt: datetimeValid RFC3339 / ISO8601 datetime
uriFormatbody.avatar: uriValid URI format (e.g. https://...)
stringTypebody.name: stringValue is a string
numberTypebody.price: numberValue is numeric (int, float)
booleanTypebody.active: booleanValue is boolean
arrayTypebody.tags: arrayValue is an array
objectTypebody.meta: objectValue is an object/map
nullTypebody.deletedAt: nullValue is null
true / falseLiteralbody.active: trueValue is boolean true/false
contains <s>Substringbody.message: contains errorString contains substring
startsWith <s>Prefixbody.id: startsWith usr_String starts with prefix
endsWith <s>Suffixbody.email: endsWith @example.comString ends with suffix
regex <p>Patternbody.code: regex ^[A-Z]{3}\d{4}$String matches regex
oneOf <opts>Membershipbody.role: oneOf admin, editorValue matches any option
in <opts>Membershipbody.status: in active, pendingAlias for oneOf
schema: <name>Schemaschema: users/profileFull body validated against schema
schema: not <name>Negated schemaschema: not errors/validationBody must NOT match error schema

Negative Assertions with not exists

You can assert that a field is absent from the response body using not exists. This is useful for verifying that optional fields are omitted on purpose, or that error/cleanup fields are not present:

expect:
  body.deletedAt: not exists    # soft-delete field should not appear
  body.error: not exists        # no error on success path
  body.archived: not exists     # field completely absent from payload

💡 Intelligent Spelling Suggestion Engine

If you write an assertion for a path that does not exist in the response payload, Gherkio's smart diff engine doesn't just fail; it analyzes the keys that were actually present in the response and prints helpful spelling suggestions in your terminal:

✗ timing.duration = lte 500ms (actual: 312ms)
✗ body.accessToken = exists
  Reason: field 'accessToken' not found in response body
  Suggestions:
    - message
    - statusCode

1.1 JSONPath Assertions ($.<path>)

For complex payloads where dot-notation or array indexing are insufficient (e.g. querying elements dynamically based on filter criteria, wildcards, or deep scans), Gherkio supports standard JSONPath expressions.

Any assertion path starting with $. will automatically be evaluated as a JSONPath expression against the response body.

Syntax & Examples:

expect:
  # Verify that the ID of the first book in the list is a number
  $.store.book[0].price: 8.95

  # Verify that all books under store have a category
  $.store.book[*].category: exists

  # Extract values matching filter conditions and assert
  $.store.book[?(@.price < 10)].title: contains "Sayings"

2. HTTP Headers (headers.<key>)

The headers. prefix allows you to inspect HTTP response headers.

Key Syntax Features:

  • Case-Insensitivity: In accordance with the HTTP RFC, header lookups are fully case-insensitive. Gherkio normalizes these keys internally, meaning headers.content-type, headers.Content-Type, and headers.CONTENT-TYPE are completely equivalent.
  • Multi-Value Headers: If a header has multiple values, Gherkio normalizes them to a single comma-separated string for easy assertion matching (e.g. headers.cache-control: contains no-cache).

3. Automatic JWT Claim Decoding (jwt.<claim>)

One of Gherkio's most powerful native features is the automatic discovery and decoding of JSON Web Tokens.

How It Works:

  1. When a request completes, Gherkio scans the response body for standard JWT field names:
    • token
    • accessToken
    • access_token
    • idToken
    • id_token
  2. If any of these fields are present and contain a valid base64-encoded, three-part JWT structure (header.payload.signature), Gherkio automatically decodes the payload section into memory.
  3. You can assert directly against claims in the payload using the jwt. prefix.

Example:

If the server returns:

{
  "accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjMiLCJyb2xlIjoiYWRtaW4iLCJleHAiOjE3MTY4ODQwMDB9.sig"
}

You can immediately assert:

expect:
  jwt.sub: "123"
  jwt.role: "admin"
  jwt.exp: datetime  # Asserts that the expiration claim is present

4. JSON Schema Contracts (schema)

For absolute validation of deep objects and API payloads, individual key assertions can become unwieldy. The schema directive enforces full, deep-object JSON Schema validation.

Syntax:

expect:
  schema: path/to/schema

How It Works:

  • Path Resolution: Gherkio looks for the schema file under your project's configured schemas directory (usually .gherkio/schemas/). It automatically appends .yaml, .yml, or .json to locate the file (e.g., schema: users/profile resolves to .gherkio/schemas/users/profile.yaml).
  • Negation (not): You can assert that a response does not match a schema by prefixing the value with not (e.g. schema: not errors/validation). This is ideal for testing error states where a validation error structure should not be returned.

5. Response Timing Assertions (timing)

To enforce service-level agreements (SLAs) or target performance constraints, Gherkio includes a native latency-checking block. This is configured at the step level (outside the expect block):

  • Syntax: timing: { max: <duration> }
  • Duration Syntax: A numeric value followed by ms (milliseconds) or s (seconds).
  • Result: It generates a timing.duration = lte <duration> assertion result in the test output.
steps:
  - request:
      method: GET
      url: /v1/users
    timing:
      max: 500ms   # Step fails if request takes longer than 500ms
    expect:
      status: 200

🔄 Dynamic Variables in Assertions

To avoid hardcoding values and to support end-to-end testing, Gherkio supports dynamic variable lookup directly in your assertion values.

1. Interpolating Saved & Global Variables ($<variable>)

Expected values inside the expect block are fully evaluated through the variable interpolation engine. This includes:

  • Saved Variables: Values extracted from previous steps using the save block (e.g. $userId).
  • Environment & Credentials Variables: Values loaded from the current environment YAML or credentials accounts (e.g. $accounts.admin.email).
steps:
  - request:
      method: POST
      url: /v1/users
      body:
        email: "bob@example.com"
    save:
      newUserId: body.id

  - request:
      method: GET
      url: /v1/users/$newUserId
    expect:
      status: 200
      # Validates that the returned ID matches the ID saved from the previous step
      body.id: $newUserId
      # Validates against a global test account email from your environment config
      body.invitedBy: $accounts.admin.email

2. Current Request References (request.body.<field>)

When verifying creation or update steps, you often want to assert that the response body contains the exact same data sent in the request. You can refer to the current request's body fields dynamically using the request.body. prefix.

steps:
  - request:
      method: POST
      url: /v1/products
      body:
        title: "Mechanical Keyboard"
        price: 99.99
    expect:
      status: 201
      # Asserts that the response payload echoes back the sent request fields dynamically
      body.data.title: request.body.title
      body.data.price: request.body.price

🗄️ Collection & Array Assertions

Gherkio provides advanced built-in collection resolvers to perform validations across lists without writing loops.

1. Array Size Validation (count(<array-path>))

Asserts the exact length of a list returned in the response.

  • Exact Count: count(body.items): 5
  • Comparators: Append .gt, .gte, .lt, or .lte suffixes to the path to perform numeric bounds checks:
    • count(body.items).gte: 1 — Asserts that the array is not empty (has at least 1 item).
    • count(body.items).lt: 10 — Asserts that the array contains fewer than 10 items.

[!NOTE] Unlike standard body paths, count() paths resolve directly against the parsed response body root. The body. prefix inside the parenthesis is optional, but supported.


2. Universal Collection Asserts (all(<array-path>))

Asserts that every single element in a list satisfies a specific condition.

  • Equality Check: all(body.items.status): active — Asserts that every item in the items list has a status equal to "active".
  • Matcher Assertion: all(body.items.price): number — Asserts that the price field in every element is a number.
  • Negative Assertion: all(body.items.errors): not exists — Asserts that the errors field is absent from every element.
ConditionExampleDescription
Literal valueall(body.items.role): adminEvery element's field equals the value
Type matcherall(body.items.price): numberEvery element's field matches a type matcher
Format matcherall(body.items.email): emailEvery element's field matches a format matcher
Existenceall(body.items.id): existsEvery element has the field
Absenceall(body.items.errors): not existsNo element has the field
Collection matcherall(body.items.count): gte 1Every array element satisfies a count condition

🌟 Comprehensive Real-World Example

Below is a complete, production-grade Gherkio test step demonstrating status codes, headers, nested body lookups, collection checks, automatic JWT assertions, and schema contract verification running side-by-side:

scenario: Complete API contract and session validation
steps:
  - request:
      method: POST
      url: /v1/auth/session
      body:
        username: "admin"
        device: "laptop"
    expect:
      # 1. Enforce exact HTTP status code
      status: 201

      # 2. Case-insensitive header checks
      headers.Content-Type: contains json
      headers.x-transaction-id: exists

      # 3. Deep-path JSON body validations using matchers & dynamic variables
      body.session.id: uuid
      body.session.metadata.device: request.body.device     # Dynamic reference to current request payload
      body.session.metadata.tenant: $tenantId               # Interpolates variable saved from previous steps
      body.session.active: true

      # 4. Collection checks with comparison bounds
      count(body.session.permissions).gte: 3
      all(body.session.permissions): string

      # 5. Automatic JWT discovery and claims validation
      jwt.role: "administrator"
      jwt.sub: uuid

      # 6. Full API schema validation (located at .gherkio/schemas/auth/session-response.yaml)
      schema: auth/session-response