JSON Assertions¶
response.assert_ok()\
.assert_json_path("user.name", "John")\
.assert_json_fragment({"status": "active"})\
.assert_json_count(5, path="items")\
.assert_json_structure({"id": int, "name": str})\
.assert_json_is_list()
Scoped JSON assertions with assert_json¶
Scope assertions to a nested path in the JSON response. The closure receives an AssertableJson bound to the resolved sub-tree; after it returns, the outer chain continues at the response level.
response.assert_ok().assert_json("data.user", lambda user: (
user
.where("id", 1)
.where_type("email", str)
.where("age", lambda v: v >= 18) # predicate
.missing("password")
.has("profile")
.json("profile", lambda p: p.where("verified", True)) # nesting
)).assert_json_path("meta.version", "1.0")
Inside an AssertableJson scope the assert_ prefix is dropped for brevity. Available methods:
| Method | Purpose |
|---|---|
has(key, count=None) |
Path exists, optionally with item count |
missing(key) |
Path does not exist |
where(key, expected) |
Value matches (exact or callable predicate) |
where_not(key, value) |
Value does not equal value (e.g. where_not("name", None)) |
where_truthy(key) |
Value is truthy (not None, 0, "", [], {}, False) |
where_falsy(key) |
Value is falsy |
where_type(key, type) |
Value is an instance of type |
count(n) |
Current scope has n items |
fragment(dict) / missing_fragment(dict) |
Subset match / absence |
exact(value) |
Full equality |
is_dict() / is_list() |
Type assertion |
structure(schema) |
Keys + types schema validation |
json(path, callback=None) |
Scope into a sub-path (recursive) |
matches_schema(schema) |
Validate against a JSON Schema (dict, file path, or URL) |
For ad-hoc use, assert_json() without a callback returns the AssertableJson directly:
root = response.assert_json()
root.has("data").where_type("data.users", list)
scoped = response.assert_json("data.users.0")
scoped.where("name", "Alice").where_type("id", int)
JSON Schema Validation¶
Validate entire JSON responses against a JSON Schema for contract testing. Accepts inline dicts, local files, or remote URLs.
# Inline schema
response.assert_ok().assert_json_schema({
"type": "object",
"properties": {
"id": {"type": "integer"},
"name": {"type": "string"},
"email": {"type": "string", "format": "email"},
},
"required": ["id", "name", "email"],
})
Schema from a local file¶
Keep your schemas alongside your tests or in a shared schemas/ directory:
from pathlib import Path
SCHEMAS = Path(__file__).parent / "schemas"
def test_user_api(client):
client.get("/api/users/1/").assert_ok().assert_json_schema(SCHEMAS / "user.json")
A plain string path works too:
Schema from a URL¶
Chaining with other assertions¶
assert_json_schema returns Self, so it chains naturally with every other assertion:
client.get("/api/users/1/")\
.assert_ok()\
.assert_json_schema(SCHEMAS / "user.json")\
.assert_json_path("name", "Alice")\
.assert_header("Content-Type", "application/json")
Scoped schema validation¶
Because matches_schema lives on AssertableJson, it works inside scoped callbacks too — validate a nested fragment against its own schema:
user_schema = {"type": "object", "properties": {"id": {"type": "integer"}, "name": {"type": "string"}}, "required": ["id", "name"]}
response.assert_json("data.user", lambda user: (
user.matches_schema(user_schema)
))
Error messages¶
When validation fails, the error message includes the exact path and reason: