Skip to content

Commit 5d6d494

Browse files
test: Merge pull request #182 from pythoninthegrasses/pythoninthegrass/integration-tests
Add integration test suite with TestClient and pytest markers
2 parents af310fd + 57e5b68 commit 5d6d494

4 files changed

Lines changed: 46 additions & 20 deletions

File tree

backlog/tasks/task-005 - Add-integration-test-suite-with-server-lifecycle-management.md

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
---
22
id: TASK-005
33
title: Add integration test suite with server lifecycle management
4-
status: To Do
4+
status: Done
55
assignee: []
66
created_date: '2026-02-26 18:06'
7+
updated_date: '2026-02-26 18:23'
78
labels:
89
- testing
910
dependencies: []
@@ -23,9 +24,15 @@ Create integration test suite that tests component interactions (FastAPI routes
2324

2425
## Acceptance Criteria
2526
<!-- AC:BEGIN -->
26-
- [ ] #1 pytest marker `integration` registered in pyproject.toml
27-
- [ ] #2 test_smoke.py refactored to use integration marker and TestClient (no manual server needed)
28-
- [ ] #3 Integration tests can run against a real or test database
29-
- [ ] #4 task test:integration in taskfile handles server start/stop automatically
30-
- [ ] #5 Server health check wait loop before test execution
27+
- [x] #1 pytest marker `integration` registered in pyproject.toml
28+
- [x] #2 test_smoke.py refactored to use integration marker and TestClient (no manual server needed)
29+
- [x] #3 Integration tests can run against a real or test database
30+
- [x] #4 task test:integration in taskfile handles server start/stop automatically
31+
- [x] #5 Server health check wait loop before test execution
3132
<!-- AC:END -->
33+
34+
## Final Summary
35+
36+
<!-- SECTION:FINAL_SUMMARY:BEGIN -->
37+
## Changes\n\n### pyproject.toml\n- Add `[tool.pytest.ini_options]` section with `markers` for `unit`, `integration`, `e2e`, and `property` test categories\n\n### tests/conftest.py\n- Add `_load_env_file()` helper to load `app/.env` when present (respects existing env vars)\n- Add `integration_client` fixture that provides a FastAPI `TestClient`, skipping gracefully when env vars or DB are unavailable\n\n### tests/test_smoke.py\n- Replace `requests`-based smoke test with `TestClient`-based integration test\n- Add `@pytest.mark.integration` marker\n- Split into `TestHealthz` class with `test_status_code` and `test_response_body` methods\n- No longer requires a running server (uses in-process TestClient)\n\n### Pre-existing (no changes needed)\n- `taskfiles/pytest.yml` already implements `test:integration` with server start/defer-stop/wait lifecycle\n- `taskfile.yml` already delegates `test:integration` to `pytest:test:integration`"
38+
<!-- SECTION:FINAL_SUMMARY:END -->

pyproject.toml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,14 @@ test = [
6161
"pytest-xdist<4.0.0,>=3.6.1",
6262
]
6363

64+
[tool.pytest.ini_options]
65+
markers = [
66+
"unit: unit tests (no external dependencies)",
67+
"integration: integration tests (component interactions, TestClient)",
68+
"e2e: end-to-end tests (requires running server)",
69+
"property: property-based tests (Hypothesis)",
70+
]
71+
6472
[tool.deptry]
6573
# DEP003: transitive deps
6674
ignore = [

tests/conftest.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,3 +64,20 @@ def mock_env():
6464
}
6565
with patch("decouple.config", side_effect=lambda key, **kwargs: defaults.get(key, kwargs.get("default", ""))):
6666
yield defaults
67+
68+
69+
@pytest.fixture
70+
def integration_client():
71+
"""TestClient for integration tests against the FastAPI app.
72+
73+
Requires app/.env or equivalent environment variables.
74+
Skips if the app cannot be imported due to missing configuration.
75+
"""
76+
try:
77+
from app.main import app
78+
except Exception as exc:
79+
pytest.skip(f"Cannot import app (missing env vars or DB): {exc}")
80+
from fastapi.testclient import TestClient
81+
82+
with TestClient(app) as client:
83+
yield client

tests/test_smoke.py

Lines changed: 8 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,12 @@
11
import pytest
2-
import requests
3-
from decouple import config
4-
5-
base_url = config('URL', default='http://localhost')
6-
if base_url == 'http://localhost':
7-
port = config('PORT', default='3000')
8-
url = f"{base_url}:{port}"
9-
else:
10-
url = base_url
11-
12-
response = requests.get(f"{url}/healthz")
132

143

154
@pytest.mark.integration
16-
def test_healthz_endpoint():
17-
assert response.status_code == 200, f"Expected status code 200, got {response.status_code}"
18-
assert response.text == '{"status":"ok"}', f"Unexpected response content: {response.text}"
5+
class TestHealthz:
6+
def test_status_code(self, integration_client):
7+
response = integration_client.get("/healthz")
8+
assert response.status_code == 200, f"Expected status code 200, got {response.status_code}"
9+
10+
def test_response_body(self, integration_client):
11+
response = integration_client.get("/healthz")
12+
assert response.json() == {"status": "ok"}, f"Unexpected response content: {response.text}"

0 commit comments

Comments
 (0)