Testing
Semblance is built for testing. Use the test client with pytest for fast, deterministic API tests.
test_client
from semblance import SemblanceAPI, test_client
api = SemblanceAPI()
@api.get("/users", input=UserQuery, output=list[User])(lambda: None)
app = api.as_fastapi()
client = test_client(app)
r = client.get("/users?name=testuser")
assert r.status_code == 200
data = r.json()
assert all(u["name"] == "testuser" for u in data)
# data: [{"name": "testuser", "created_at": "2024-08-21T09:22:43.516168"}, ...]
test_client wraps your FastAPI app in Starlette's TestClient. No server process is started.
Deterministic Seeding
Use a fixed seed for reproducible tests:
api = SemblanceAPI(seed=42)
Or per-request via seed_from:
class UserQueryWithSeed(BaseModel):
name: str = "alice"
seed: int | None = None
@api.get("/users", input=UserQueryWithSeed, output=list[User], seed_from="seed")
def users():
pass
# Same seed → same responses
r1 = client.get("/users?name=x&seed=99")
r2 = client.get("/users?name=x&seed=99")
assert r1.json()[0]["created_at"] == r2.json()[0]["created_at"]
Pytest Fixture
# conftest.py
import pytest
from semblance import SemblanceAPI, test_client
@pytest.fixture
def api():
api = SemblanceAPI(seed=42)
api.get("/users", input=UserQuery, output=list[User])(lambda: None)
return api
@pytest.fixture
def client(api):
return test_client(api.as_fastapi())
# test_users.py
def test_users_return_list(client):
r = client.get("/users?name=alice")
assert r.status_code == 200
assert isinstance(r.json(), list)
Property-based Testing
For Schemathesis-style testing, use Hypothesis with Semblance's property-testing helpers (requires hypothesis, included in [dev]):
from hypothesis import given
from semblance import SemblanceAPI
from semblance.property_testing import strategy_for_input_model
from semblance.testing import test_client
api = SemblanceAPI(seed=42)
api.get("/users", input=UserQuery, output=list[User], list_count=2)(lambda: None)
app = api.as_fastapi()
client = test_client(app)
strategy = strategy_for_input_model(UserQuery)
@given(strategy)
def test_users_valid_response(inp):
r = client.get("/users", params=inp.model_dump())
assert r.status_code == 200
from pydantic import TypeAdapter
TypeAdapter(list[User]).validate_python(r.json())
test_users_valid_response()
- strategy_for_input_model(model, path_template=None) — builds a Hypothesis strategy that generates instances of the input model (handles
Annotated, optional fields, nested models). - test_endpoint(client, method, path, input_strategy, output_model, ...) — runs a
@giventest: draws input, calls the endpoint, asserts status and validates response; supports optional invariants.
Use property-based tests to ensure many generated inputs produce valid responses and to catch schema drift.
Contract and Snapshot Testing
- Assert response shape matches your output model.
- Use deterministic seeds for snapshot-style checks.
- Test validation: send invalid input and assert
422responses.