Files
openapi-first/docs/wiki/07_testing.md

138 lines
3.7 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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
```
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:
```bash
pytest # 23 tests, zero mocks of the library
pytest -q
pytest tests/test_loader.py
```
---
## 🏗️ 2. The Fixture Pattern
```python
# 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
```python
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)
```python
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
```python
def test_loader_invalid_spec_fails():
with pytest.raises(OpenAPISpecLoadError):
load_openapi("broken.yaml")
```
### 3.4 Client contract drift
```python
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:
```bash
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:
```bash
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
---
## Related
- [01 Overview](01_overview.md) · [02 Components](02_components.md) · [04 Error Handling](06_error_handling.md)