Pytest Integration Tutorial
Estimated time: 15-20 minutes
This tutorial shows how to integrate Schemathesis into your pytest
test suite using a Booking API.
CLI vs Pytest Integration
The CLI offers more features (API probes, multiple phases, advanced reporting). Use pytest integration when you need direct integration with existing pytest
test suites.
Prerequisites
- Git - to clone the example API repository
- Docker Compose - Install Docker Desktop which includes Docker Compose
- uv - Python package manager to install pytest and Schemathesis
- Python 3.9+ - this tutorial uses Python 3.13
Install dependencies:
Shell differences
This tutorial uses the Fish shell. Depending on your setup, you will need to adjust the source
command based on the output of uv venv
Verify your setup:
API under test
We'll test a booking API that handles hotel reservations - creating bookings and retrieving guest information.
The API lives in the Schemathesis repository:
git clone https://github.com/schemathesis/schemathesis.git
cd schemathesis/examples/basic
docker compose up -d
Verify the API is running
Open http://localhost:8080/docs - you should see the interactive API documentation.
Authentication token
The API requires bearer token authentication. Use: secret-token
First Test Run
Create your first Schemathesis pytest test:
Create test_api.py
:
import schemathesis
schema = schemathesis.openapi.from_url(
"http://127.0.0.1:8080/openapi.json",
)
# To show the token in the cURL snippet
schema.config.output.sanitization.update(enabled=False)
@schema.parametrize()
def test_api(case):
case.call_and_validate(headers={"Authorization": "Bearer secret-token"})
Run the test:
Output shows the same bug:
test_api.py::test_api[POST /bookings] FAILED
test_api.py::test_api[GET /bookings/{booking_id}] PASSED
test_api.py::test_api[GET /health] PASSED
================================== FAILURES ===================================
__________________________ test_api[POST /bookings] ___________________________
+ Exception Group Traceback (most recent call last):
| File "/schemathesis-tutorial/test_api.py", line 10, in test_api
| case.call_and_validate(headers={"Authorization": "Bearer secret-token"})
| ~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
| File "/../schemathesis/generation/case.py", line 185, in call_and_validate
| self.validate_response(
| ~~~~~~~~~~~~~~~~~~~~~~^
| response,
| ^^^^^^^^^
| ...<4 lines>...
| transport_kwargs=kwargs,
| ^^^^^^^^^^^^^^^^^^^^^^^^
| )
| ^
| File "/schemathesis/generation/case.py", line 171, in validate_response
| raise FailureGroup(_failures, message) from None
| schemathesis.FailureGroup: Schemathesis found 2 distinct failures
|
| - Server error
|
| - Undocumented HTTP status code
|
| Received: 500
| Documented: 200, 422
|
| [500] Internal Server Error:
|
| `Internal Server Error`
|
| Reproduce with:
|
| curl -X POST -H 'Authorization: Bearer secret-token' \
| -H 'Content-Type: application/json' \
| -d '{"guest_name": "00", "nights": 1, "room_type": ""}' \
| http://127.0.0.1:8080/bookings
|
| (2 sub-exceptions)
+-+---------------- 1 ----------------
Empty room_type
causes a 500 error because the pricing logic can't handle unexpected values.
Run the provided curl
command to reproduce this failure.
Fixing the bug
Root cause
The schema allows any string for room_type
, but our pricing logic only handles specific values.
Fix: Constrain room types in the schema
Don't forget to restart:
Re-running the tests
Now let's verify our fix by re-running the tests. Focus on the operation you just fixed:
import schemathesis
schema = ... # snip
@schema.include(operation_id="create_booking_bookings_post").parametrize()
def test_api(case):
case.call_and_validate(headers={"Authorization": "Bearer secret-token"})
Operation ID explained
FastAPI generates operation IDs automatically. You can find them in the OpenAPI schema or API docs.
The operation_id="create_booking_bookings_post"
option targets only the specific operation we fixed, making the test run faster during development. You can also filter by HTTP method (method="POST"
) or path patterns (path_regex="/bookings/.*"
).
Generating more test cases
Let's be more thorough:
import schemathesis
from hypothesis import settings
schema = ... # snip
@schema.parametrize()
@settings(max_examples=500)
def test_api(case):
case.call_and_validate(headers={"Authorization": "Bearer secret-token"})
max_examples=500
generates more test cases per operation, increasing the chance of finding edge cases that smaller test runs might miss.
The trade-off is longer execution time, but you'll get more chances to find bugs.
Hypothesis configuration
See the whole list of available settings in the Hypothesis documentation.
Configuration File
Create schemathesis.toml
in your project:
# Core settings from our previous tests
headers = { Authorization = "Bearer ${API_TOKEN}" }
[output.sanitization]
enabled = false
[generation]
max-examples = 500
Now your tests look like this:
import schemathesis
schema = schemathesis.openapi.from_url(
"http://127.0.0.1:8080/openapi.json",
)
@schema.parametrize()
def test_api(case):
case.call_and_validate()
Schemathesis automatically loads schemathesis.toml
from the current directory or project root. You can specify a custom configuration file:
import schemathesis
config = schemathesis.Config.from_path(
"path-to-my/config.toml"
)
schema = schemathesis.openapi.from_url(
"http://127.0.0.1:8080/openapi.json",
config=config
)
What's next?
Continue learning:
- Python API - Complete Python API reference
- Configuration Reference - All configuration options
- How Schemathesis Integrates with Pytest