Skip to content

Testing β€” Smoke-First, Contract-First

Everything openapi-first ships is tested against real specs, real handlers, and real HTTP through FastAPI's TestClient and the bundled templates. There are no fakes of the library itself.


πŸ§ͺ 1. Suite Overview

1
2
3
4
5
6
tests/
β”œβ”€β”€ conftest.py        # fixtures: spec_file, routes_module, app, client
β”œβ”€β”€ test_app.py        # OpenAPIFirstApp: routes served, overrides, fail-fast
β”œβ”€β”€ test_binder.py     # bind_routes: opId resolution + missing-handler failures
β”œβ”€β”€ test_loader.py     # load_openapi: json/yaml, env resolution, validation
└── test_client.py     # OpenAPIClient: opIdβ†’callable, params, error cases

Run with:

1
2
3
pytest            # 23 tests, zero mocks of the library
pytest -q
pytest tests/test_loader.py

πŸ—οΈ 2. The Fixture Pattern

# conftest.py (abridged)
@pytest.fixture
def spec_file(tmp_path):
    path = tmp_path / "openapi.json"
    path.write_text(json.dumps(SPEC), encoding="utf-8")
    return str(path)

@pytest.fixture
def app(spec_file):
    return OpenAPIFirstApp(
        openapi_path=spec_file,
        routes_module=routes_module(),
    )

@pytest.fixture
def client(spec_file):
    return OpenAPIClient(json.loads(Path(spec_file).read_text()))

πŸ§ͺ 3. What's Actually Asserted

3.1 App-level smoke

1
2
3
4
def test_app_routes_served(spec_file):
    app = OpenAPIFirstApp(openapi_path=spec_file, routes_module=routes)
    client = TestClient(app)
    assert client.get("/health").json() == {"status": "ok"}

3.2 Fail-fast (the heart)

1
2
3
def test_app_missing_handler_fails_at_startup(spec_file):
    with pytest.raises(MissingOperationHandler):
        OpenAPIFirstApp(openapi_path=spec_file, routes_module=empty_routes)

3.3 Loader validation

1
2
3
def test_loader_invalid_spec_fails():
    with pytest.raises(OpenAPISpecLoadError):
        load_openapi("broken.yaml")

3.4 Client contract drift

1
2
3
def test_client_duplicate_operation_id_raises():
    with pytest.raises(OpenAPIClientError):
        OpenAPIClient(dup_spec)

πŸ›οΈ 4. Testing the Templates

Each bundled template ships its own test + in-memory store, so you get a runnable contract test the moment you scaffold:

1
2
3
openapi-first scaffold crud_app my-service
cd my-service
pytest -q

test_crud_app.py / test_model_app.py / test_vet_app.py exercise the full CRUD surface through TestClient β€” including 201/204 status codes, 404s, and (in vet_app) SSE streaming via StreamingResponse.


πŸ•ΉοΈ 5. Quality Gates (CI)

The same gates the library itself must pass are what keep the docs honest:

Gate Purpose
black --check formatting parity
ruff check lint hygiene
mypy type safety (strict, --disable-error-code where intentional)
pytest 23 tests, green
coverage tracked via pytest-cov (HTML + XML + term)

Run the whole gate locally:

1
2
3
4
black --check openapi_first tests
ruff check openapi_first tests
mypy openapi_first
pytest

πŸ’‘ 6. Testing Tips

  • Start from startup: assert OpenAPIFirstApp(...) raises for the broken contracts β€” those are your most valuable tests
  • Client ↔ server: smoke a client against the same spec the app was built from β€” one spec, two sides, zero drift
  • Templates are scaffolds: their tests are copyable starting points, not canonical suites
  • No mocking of the library: exercise loader β†’ binder β†’ app β†’ client as a real pipeline