schemathesis-fuzzing
Generates property-based API tests automatically from an OpenAPI 2/3.x or GraphQL schema using Schemathesis, runs them via the `schemathesis run` CLI or as a pytest decorator, configures the canonical checks (status_code_conformance, response_schema_conformance, content_type_conformance, response_headers_conformance, not_a_server_error), and gates CI on schema-conformance failures plus 5xx detection. Use when the project ships an OpenAPI or GraphQL schema and the team wants schema-driven coverage that scales as the API evolves.
Install with skills.sh (any agent)
npx skills add testland/qa --skill schemathesis-fuzzingschemathesis-fuzzing
Overview
Schemathesis is a property-based API testing tool that "automatically generates property-based tests from your OpenAPI or GraphQL schema and exercises the edge cases that break your API" (schemathesis-readme (opens in new window)). The schema is the single source of truth - every endpoint, parameter, and response shape becomes a generator that produces hundreds of targeted variations per run.
This is complementary to example-based API testing (postman-collections, tavern-testing, restassured-testing, karate-testing) - example-based tests verify happy paths; Schemathesis attacks the boundaries the team forgot.
When to use
If the API has no schema, Schemathesis cannot help - generate a schema first (FastAPI, Flask-Smorest, NestJS Swagger, dropwizard, or hand-author a spec), then return.
How to use
Install
pip install schemathesis(Per schemathesis-readme (opens in new window).)
For the latest CLI without modifying the project's Python env:
uvx schemathesis run <schema-url>(Adapted from schemathesis-docs (opens in new window) - uvx is the uv runner for one-off tool execution.)
Running via CLI
Basic invocation per schemathesis-readme (opens in new window):
schemathesis run <schema-url><schema-url> can be:
Key flags
Per schemathesis-readme (opens in new window):
| Flag | Purpose |
|---|---|
--base-url <url> | Override the API base URL (test against staging vs prod). |
--checks <name> (repeatable) | Restrict to specific validations. |
--hypothesis-max-examples <N> | Number of generated cases per endpoint. |
--workers <N> | Parallel workers; speeds up large schemas. |
--header 'X-API-KEY: ...' | Inject auth header on every generated request. |
--auth user:pass | HTTP Basic Auth. |
--cassette-har <path> | Record / replay using HAR files (debug aid). |
Worked example
schemathesis run https://api.example.com/openapi.json \
--base-url https://staging.example.com \
--checks status_code_conformance \
--checks response_schema_conformance \
--checks not_a_server_error \
--hypothesis-max-examples 200 \
--workers 4 \
--header "Authorization: Bearer $API_TOKEN"Built-in checks
Per schemathesis-readme (opens in new window), the canonical checks:
| Check | What it verifies |
|---|---|
status_code_conformance | Response status code is one of the codes documented in the schema for that endpoint. |
response_schema_conformance | Response body matches the documented schema (types, required fields, enum values). |
content_type_conformance | Content-Type header is one of the schema's documented media types. |
response_headers_conformance | Response headers conform to the schema's headers declaration. |
not_a_server_error | The response is not in the 5xx range; any 5xx is a hard fail. |
Run all checks (default), or restrict via repeated --checks:
# Strict: every check active
schemathesis run <schema>
# Only flag 5xx errors (cheap smoke test)
schemathesis run <schema> --checks not_a_server_errorA failing check produces a deterministic reproduction - Schemathesis prints the exact curl command and Hypothesis seed to reproduce the generated request.
Pytest integration
For projects that want Schemathesis cases as first-class pytest tests (schemathesis-readme (opens in new window)):
# tests/api/test_schemathesis.py
import schemathesis
schema = schemathesis.openapi.from_url("https://your-api.com/openapi.json")
@schema.parametrize()
def test_api(case):
case.call_and_validate()@schema.parametrize() generates one pytest test per endpoint × method combination. case.call_and_validate() issues the generated request and runs every default check.
For project-specific auth injection, header signatures, or response post-processing, use before_call hooks - see references/ci-and-pytest-integration.md.
Operating in CI
Gate PRs on Schemathesis with a shallow per-PR run (--hypothesis-max-examples 50) and a deeper nightly cron (200+), always against staging via --base-url. Hypothesis shrinks any failure to a minimal reproducer, so a 50-example PR run catches most regressions while the nightly depth surfaces the rare ones. The full GitHub Actions workflow (JUnit reporting, artifact upload) and the per-PR / nightly / weekly cadence table live in references/ci-and-pytest-integration.md.
Anti-patterns
| Anti-pattern | Why it fails | Fix |
|---|---|---|
Running with --checks not_a_server_error only and calling it done | 5xx detection misses 4xx-with-wrong-content cases. | Run all default checks; 5xx-only is a smoke check, not coverage. |
--hypothesis-max-examples 5 to keep CI fast | Coverage too thin; flaky-looking results. | 50+ on PR; if too slow, parallelize via --workers. |
| Targeting production URL | Generated requests can mutate prod data; 5xx alerts trigger oncall. | Always --base-url <staging>; production should never see fuzz traffic. |
| Stale schema URL | Schemathesis fuzzes the schema's authoritative version, not what the deploy actually serves; false negatives mask bugs. | CI fetches the schema from the PR's deployed staging artifact, not from a checked-in copy. |
| Ignoring shrunk failures | A not_a_server_error failure with a 5-byte input is a real bug, not noise. | Triage every failure; close as "won't fix" only with a documented schema-amendment plan. |
Limitations
References
Schemathesis CI wiring and advanced pytest hooks
View source (opens in new window)Schemathesis CI wiring and advanced pytest hooks
Deep reference for schemathesis-fuzzing SKILL.md. Consult when wiring Schemathesis into CI, or when the pytest integration needs project-specific auth / header injection.
Advanced pytest integration - hooks
For finer-grained integration than the basic @schema.parametrize() shown in SKILL.md (schemathesis-readme (opens in new window)):
@schema.parametrize()
@schemathesis.hook("before_call")
def add_auth(context, case):
case.headers["Authorization"] = f"Bearer {os.environ['API_TOKEN']}"
def test_api_with_auth(case):
case.call_and_validate()Hooks let the team inject project-specific auth, header signatures, or response post-processing without forking Schemathesis.
CI integration
# .github/workflows/api-fuzz.yml
name: api-fuzz
on:
pull_request:
push:
branches: [main]
schedule:
- cron: '0 6 * * *' # nightly broader run
jobs:
schemathesis:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: actions/setup-python@v5
with:
python-version: '3.12'
- run: pip install schemathesis
- name: Schemathesis run
env:
API_TOKEN: ${{ secrets.STAGING_API_TOKEN }}
run: |
schemathesis run https://staging.example.com/openapi.json \
--base-url https://staging.example.com \
--hypothesis-max-examples 50 \
--workers 4 \
--header "Authorization: Bearer $API_TOKEN" \
--junit-xml=results.xml
- name: Upload report
if: always()
uses: actions/upload-artifact@v4
with:
name: schemathesis-results
path: results.xml
retention-days: 14
- name: Surface JUnit
if: always()
uses: dorny/test-reporter@v1
with:
name: API fuzz
path: results.xml
reporter: java-junitThe PR-trigger run uses lower --hypothesis-max-examples (e.g. 50) for fast feedback; the nightly cron run uses higher values (200+) for deeper coverage. This separation keeps PR CI under 10 minutes without sacrificing nightly depth.
Two complementary CI cadences
| Cadence | Examples per endpoint | Purpose |
|---|---|---|
| Per-PR | 50 | Fast smoke; catch obvious schema-drift breaks. |
| Nightly | 200-500 | Deep coverage; surface rarely-triggered edge cases. |
| Weekly | 1000+ | Pre-release deep validation. |
Hypothesis (the underlying property-based engine) shrinks failures to minimal reproducers automatically - a 200-example PR run is plenty to surface most regressions; nightly depth catches the rare ones.
Related skills
api-chaos-runner
Runs the project's existing API tests under injected network chaos - latency, timeouts, dropped connections, bandwidth caps, packet loss - via Toxiproxy (notes on Pumba / Gremlin / LitmusChaos). Builds a per-scenario chaos matrix and reports which assertions break under which conditions, verifying resilience patterns (retry, circuit-breaker, timeout, fallback). Unlike schemathesis-fuzzing and restler-fuzzing, which generate new tests from a schema, this drives your EXISTING example-based suite.
karate-testing
Authors Karate `.feature` files using its Gherkin-flavored DSL for HTTP API tests, leverages the `match` keyword with fuzzy validators (#number / #string / #regex / contains / arrays), runs the suite via JUnit 5 plus Maven Surefire, and produces JUnit XML for CI gating. Use when the project is on the JVM and prefers a feature-file authoring flow over Java-DSL fluent chains; for those fluent chains use restassured-testing, for the same YAML-style flow on a Python/pytest stack use tavern-testing.
postman-collections
Repairs Postman and Newman runs in CI - a reporter that never writes the HTML file the pipeline expects, a nightly job that fires requests as fast as it can until a partner API rate-limits it, or a report with one row per request when the collection asserts a dozen things between them. Authors Postman collections (requests, tests, variables, environments), runs them headless via the Newman CLI, configures reporters (cli / json / junit / html) for CI artifact upload, and drives data-driven runs from JSON / CSV iteration files. Use when HTTP API tests are authored in Postman and need to run, pace themselves, and report correctly in CI.
restassured-testing
Strengthens and speeds up JVM API test suites - assertions so loose that an empty `200 OK` passed for three days, a 220-test suite spending fourteen minutes mostly waiting on sequential HTTP calls, or endpoints exercised through a hand-rolled JDK HTTP client with JSON parsed by hand. Authors REST Assured (Java) tests in the given().when().then() BDD-style DSL: status code and JSON / XML path assertions, authentication (Basic, OAuth2, API key), Maven / Gradle dependencies, JUnit 5 execution, and Surefire / JaCoCo reports for CI gating. Use when the project is on the JVM and its API tests miss real assertions, run too slowly, or are written by hand.
restler-fuzzing
Runs stateful REST API fuzzing using Microsoft's RESTler - infers producer-consumer dependencies from an OpenAPI spec, drives sequences of requests (POST → GET → DELETE chains), and reports 5xx errors, resource leaks, and hierarchy violations. Wraps the canonical 4-stage workflow (compile → test → fuzz-lean → fuzz). Use when the API is stateful (resources are created, queried, modified, deleted) and Schemathesis's stateless fuzzing is missing the multi-step bugs.