Skip to main content

JSON Path Assertions

For testing nested attributes or specific fields inside a JSON response body, you can use the [assert.json] subtable. This provides granular assertion rules on individual JSON nodes.

1. Syntax & Configuration

Rumour supports two equivalent TOML syntaxes for declaring JSON path assertions:

Define a separate subtable for each target path:

[request]
method = "POST"
url = "{{base_url}}/post"

[assert.json."json.id"]
equal = 123

[assert.json."json.profile.email"]
equal = "jane@example.com"
regex = "^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$"

Option B: Inline Table Syntax (Sleeker for single-constraint assertions)

Group all path assertions under a single [assert.json] block using inline TOML tables:

[request]
method = "POST"
url = "{{base_url}}/post"

[assert.json]
"json.id" = { equal = 123 }
"json.profile.email" = { equal = "jane@example.com", regex = "^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$" }

2. Operators Reference

Each JSON path block supports the following evaluation operators:

OperatorTypeDescription
equalJSON ValueAsserts that the field is exactly equal to the value (supports strings, numbers, booleans, and null).
not_equalJSON ValueAsserts that the field is not equal to the specified value.
containsStringAsserts that the targeted field contains the specified substring.
⚠️ Warning: The contains operator is strictly supported for String nodes. If used on an array, the assertion will fail.
existsBooleanChecks if the JSON path exists (true) or is absent (false) from the payload.
greater_thanFloat / IntChecks if the target numeric node is strictly greater than this value (alias greaterthan).
less_thanFloat / IntChecks if the target numeric node is strictly less than this value (alias lessthan).
regexStringAsserts that the string value matches the regular expression pattern.

3. Nested Path Traversal & Array Indexing

Rumour traverses JSON using dot notation.

A. Property Navigation

Access object children by separating keys with a dot:

{
"company": {
"name": "Acme Corp"
}
}
  • Path: "company.name" (evaluates to "Acme Corp")

B. Array Indexing Syntax

Rumour supports two syntaxes for array index traversal:

  1. Standard Dot Navigation: Treat the array index as a segment.
    • Path: "company.departments.0.name"
  2. Bracket Navigation: Enclose the index in brackets.
    • Path: "company.departments.[0].name"

Rumour automatically strips outer brackets [ and ] from any path segment before parsing it as a numerical array index.

4. JSON Assertion Examples (httpbin.org)

Here are complete request definitions asserting conditions against httpbin.org.

note

Crucial Concept: The [assert.json] block ALWAYS evaluates the RESPONSE body returned by the server, never the request you are sending!

In the examples below, we are sending a POST request to https://httpbin.org/post. This specific testing server echoes the JSON we send it back to us, nested inside a root key called "json". That is why our assertion paths start with "json." (e.g., "json.name" instead of just "name"). We are asserting on the server's response.

A. Dotted Subtable Syntax Example

# test_operators.toml
[request]
method = "POST"
url = "{{base_url}}/post"

[headers]
User-Agent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"

[body]
type = "json"
raw = '{"name": "Alice", "age": 30, "roles": ["admin", "editor"], "active": true, "nested": {"id": 123}}'

[assert.json."json.name"]
equal = "Alice"
not_equal = "Bob"
regex = "^Ali"
contains = "lic"

[assert.json."json.age"]
greater_than = 25.0
less_than = 35.0

[assert.json."json.roles.0"]
equal = "admin"

[assert.json."json.roles.[1]"]
equal = "editor"

[assert.json."json.active"]
equal = true

[assert.json."json.nested.id"]
exists = true

[assert.json."json.non_existent"]
exists = false

Successful Execution Output Trace

POST https://httpbin.org/post
URL: https://httpbin.org/post
✓ SUCCESS: /home/bugsfounder/workspace/testing/json_assert_example/users/requests/test_operators.toml (2363ms)
✓ /home/bugsfounder/workspace/testing/json_assert_example/users/requests/test_operators.toml → PASS (1 Pass, 0 Fail, 0 Skip)
╭──────────────────────────────────────────────────────────────────────────╮
│ RUMOUR EXECUTION REPORT │
├──────────────────────────────────────────────────────────────────────────┤
│ Total Requests: 1
│ Successful: 1
│ Failed: 0
│ Skipped: 0
│ Success Rate: 100.0% │
│ Total Time: 2367ms │
╰──────────────────────────────────────────────────────────────────────────╯

✓ Successful Requests:
- /home/bugsfounder/workspace/testing/json_assert_example/users/requests/test_operators.toml [200] [2367ms]

Actionable Recommendations:
→ Run with --json to export this report for your CI/CD pipeline.

Let's break down what just happened:

  1. The Live Action: Rumour executes the POST request and successfully evaluates all of our complex JSON path constraints in real-time. Because every single assertion passed perfectly, it gets a clean ✓ SUCCESS.
  2. The Final Report: The execution report summarizes the results. You get the peace of mind knowing that the complex, deeply nested JSON data is shaped exactly the way you expect it to be!

B. Inline Table Syntax Example

# test_operators_inline.toml
[request]
method = "POST"
url = "{{base_url}}/post"

[headers]
User-Agent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"

[body]
type = "json"
raw = '{"name": "Alice", "age": 30, "roles": ["admin", "editor"], "active": true, "nested": {"id": 123}}'

[assert.json]
"json.name" = { equal = "Alice", not_equal = "Bob", regex = "^Ali", contains = "lic" }
"json.age" = { greater_than = 25.0, less_than = 35.0 }
"json.roles.0" = { equal = "admin" }
"json.roles.[1]" = { equal = "editor" }
"json.active" = { equal = true }
"json.nested.id" = { exists = true }
"json.non_existent" = { exists = false }

Successful Execution Output Trace

POST https://httpbin.org/post
URL: https://httpbin.org/post
✓ SUCCESS: /home/bugsfounder/workspace/testing/json_assert_example/users/requests/test_operators_inline.toml (1854ms)
✓ /home/bugsfounder/workspace/testing/json_assert_example/users/requests/test_operators_inline.toml → PASS (1 Pass, 0 Fail, 0 Skip)
╭──────────────────────────────────────────────────────────────────────────╮
│ RUMOUR EXECUTION REPORT │
├──────────────────────────────────────────────────────────────────────────┤
│ Total Requests: 1
│ Successful: 1
│ Failed: 0
│ Skipped: 0
│ Success Rate: 100.0% │
│ Total Time: 1856ms │
╰──────────────────────────────────────────────────────────────────────────╯

✓ Successful Requests:
- /home/bugsfounder/workspace/testing/json_assert_example/users/requests/test_operators_inline.toml [200] [1856ms]

Actionable Recommendations:
→ Run with --json to export this report for your CI/CD pipeline.

Let's break down what just happened:

Just like the dotted syntax example above, Rumour completely evaluated the inline assertions and generated an identical success report. The inline syntax is simply a cleaner way to write your .toml files—under the hood, Rumour handles them exactly the same way!

5. Failing JSON Assertions & Diagnostics

If one or more JSON path assertions fail, Rumour halts execution and outputs exact details of the mismatches.

Failing Example TOML

[assert.json."json.age"]
greater_than = 35.0 # actual is 30

[assert.json."json.roles"]
contains = "admin" # FAILS: contains is not supported on arrays

Diagnostics output:

✗ FAILED: test_operators.toml (931ms) - JSON Path: json.age: Expected > 35, got 30 | JSON Path: json.roles: Value is not a string or not found (expected containment)

Let's break down what just happened:

  1. The Instant Failure: One or more of our assertions failed, so Rumour immediately halts execution and throws an ✗ FAILED error.
  2. The Detailed Post-Mortem: Look closely at the error message. Rumour doesn't just say "failed"—it tells you exactly which JSON paths broke and why, separated by a pipe (|).
    • For json.age, it explicitly states Expected > 35, got 30.
    • For json.roles, it warns you that contains is only meant for strings, but it found an array instead.

This kind of pinpoint accuracy saves you hours of digging through JSON payloads manually!