138 lines
3.7 KiB
Markdown
138 lines
3.7 KiB
Markdown
# 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)
|