Skip to content

Add assert_json_schema for JSON Schema contract testing #14

@othercodes

Description

@othercodes

Summary

Add JSON Schema validation to AssertableJson via matches_schema(), with a thin assert_json_schema() wrapper on the response mixin. Validates entire JSON response bodies against a JSON Schema. Scoped validation comes for free through existing AssertableJson architecture.

Motivation

assert_json_structure checks keys and types using a custom format. JSON Schema is the industry standard for API contract validation — it handles nested objects, required fields, oneOf/anyOf, $ref, format constraints, etc. This bridges the gap between ad-hoc assertions and formal contract testing.

API

# inline dict
response.assert_json_schema({"type": "object", "properties": {"id": {"type": "integer"}}})

# local file
response.assert_json_schema("schemas/user.json")
response.assert_json_schema(Path("schemas/user.json"))

# remote URL
response.assert_json_schema("https://api.example.com/schemas/user.json")

# scoped — works automatically via AssertableJson
response.assert_json("data.user", lambda user: (
    user.matches_schema(user_schema)
))

All return Self for chaining.

Schema sources

Source Type Behavior
Inline dict dict Used directly
Local file str or Path ending in .json Read and json.load
URL str starting with http:// or https:// Fetch via urllib.request and parse

Architecture

Follows the existing pattern where every assert_json_* method on the mixin is a thin wrapper that delegates to AssertableJson:

Layer Method Responsibility
AssertableJson matches_schema(dict | str | Path) Core: resolve schema, validate self._data
JsonContentAssertionsMixin assert_json_schema(dict | str | Path) Thin wrapper: self.assert_json().matches_schema(schema)

Scoped validation comes for free because matches_schema operates on self._data, which is already scoped by AssertableJson's existing json() method. No new mixin or file needed.

A private _resolve_schema helper in json.py handles schema source resolution (dict passthrough, file read, URL fetch). URL fetching uses urllib.request from stdlib.

Design decisions

  • Core logic on AssertableJson — consistent with every other assertion in the module (where, has, count, structure, etc.)
  • Hard dependencyjsonschema>=4.0 added to project.dependencies
  • URL support via stdliburllib.request, no extra HTTP client dependency
  • Error reporting — surface jsonschema.ValidationError.message and path in the AssertionError
  • No schema caching — caller's responsibility
  • No custom validators or format checkers — plain jsonschema.validate

File changes

File Change
src/pyssertive/http/json.py Add matches_schema() to AssertableJson, add assert_json_schema() to JsonContentAssertionsMixin, add _resolve_schema helper
pyproject.toml Add jsonschema>=4.0 to dependencies
tests/http/test_contract.py New — tests using local .json schema files
tests/schemas/ New — test schema fixture files

Metadata

Metadata

Assignees

Labels

No labels
No labels

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions