$npx -y skills add omnigentx/jarvis --skill api-testingBackend API testing with pytest, httpx, and FastAPI TestClient. Covers endpoint verification, contract testing, mock patterns, and error scenario validation. Use when QE needs to test REST APIs, verify response schemas, or mock external services.
| 1 | # API Testing |
| 2 | |
| 3 | Test backend APIs using pytest + httpx (integration) or FastAPI TestClient (unit). Write tests to `/tmp`, execute via `python`. |
| 4 | |
| 5 | ## Decision Tree |
| 6 | |
| 7 | ``` |
| 8 | API to test → Is it a FastAPI app? |
| 9 | ├─ Yes → Use TestClient (no server needed) |
| 10 | │ from fastapi.testclient import TestClient |
| 11 | │ |
| 12 | └─ No → Use httpx (server must be running) |
| 13 | import httpx |
| 14 | ``` |
| 15 | |
| 16 | ## FastAPI TestClient (Unit Tests) |
| 17 | |
| 18 | ```python |
| 19 | # /tmp/test_api.py |
| 20 | import pytest |
| 21 | from fastapi.testclient import TestClient |
| 22 | from server import app # Import your FastAPI app |
| 23 | |
| 24 | client = TestClient(app) |
| 25 | |
| 26 | def test_health(): |
| 27 | r = client.get("/health") |
| 28 | assert r.status_code == 200 |
| 29 | |
| 30 | def test_create_item(): |
| 31 | r = client.post("/items", json={"name": "test", "price": 9.99}) |
| 32 | assert r.status_code == 201 |
| 33 | data = r.json() |
| 34 | assert data["name"] == "test" |
| 35 | assert "id" in data |
| 36 | |
| 37 | def test_auth_required(): |
| 38 | r = client.get("/protected") |
| 39 | assert r.status_code == 401 |
| 40 | |
| 41 | def test_with_auth(): |
| 42 | r = client.get("/protected", headers={"Authorization": "Bearer test-token"}) |
| 43 | assert r.status_code == 200 |
| 44 | ``` |
| 45 | |
| 46 | ## httpx (Integration Tests) |
| 47 | |
| 48 | ```python |
| 49 | # /tmp/test_integration.py |
| 50 | import httpx |
| 51 | import pytest |
| 52 | |
| 53 | BASE_URL = "http://localhost:8000" # Adjust to target |
| 54 | |
| 55 | def test_endpoint_responds(): |
| 56 | r = httpx.get(f"{BASE_URL}/health") |
| 57 | assert r.status_code == 200 |
| 58 | |
| 59 | def test_crud_flow(): |
| 60 | # Create |
| 61 | r = httpx.post(f"{BASE_URL}/items", json={"name": "test"}) |
| 62 | assert r.status_code == 201 |
| 63 | item_id = r.json()["id"] |
| 64 | |
| 65 | # Read |
| 66 | r = httpx.get(f"{BASE_URL}/items/{item_id}") |
| 67 | assert r.status_code == 200 |
| 68 | |
| 69 | # Delete |
| 70 | r = httpx.delete(f"{BASE_URL}/items/{item_id}") |
| 71 | assert r.status_code == 204 |
| 72 | ``` |
| 73 | |
| 74 | ## Execution |
| 75 | |
| 76 | ```bash |
| 77 | # Run all tests |
| 78 | cd <project_dir> && python -m pytest /tmp/test_api.py -v |
| 79 | |
| 80 | # Run with coverage |
| 81 | cd <project_dir> && python -m pytest /tmp/test_api.py --cov=. --cov-report=term |
| 82 | ``` |
| 83 | |
| 84 | ## Testing Patterns |
| 85 | |
| 86 | | Pattern | When | Example | |
| 87 | |---------|------|---------| |
| 88 | | Happy path | Always first | `POST /items` → 201 | |
| 89 | | Error scenarios | Required | Invalid input → 422, Not found → 404 | |
| 90 | | Auth flows | If protected | No token → 401, Bad token → 403 | |
| 91 | | Edge cases | Important | Empty body, large payload, special chars | |
| 92 | | Contract validation | API changes | Verify response schema matches spec | |
| 93 | |
| 94 | ## Response Schema Validation |
| 95 | |
| 96 | ```python |
| 97 | def test_response_schema(): |
| 98 | r = client.get("/items/1") |
| 99 | data = r.json() |
| 100 | # Verify required fields exist |
| 101 | assert "id" in data |
| 102 | assert "name" in data |
| 103 | assert isinstance(data["id"], int) |
| 104 | assert isinstance(data["name"], str) |
| 105 | ``` |
| 106 | |
| 107 | ## Mocking External Dependencies |
| 108 | |
| 109 | ```python |
| 110 | from unittest.mock import patch, AsyncMock |
| 111 | |
| 112 | def test_with_mocked_service(): |
| 113 | with patch("services.external_api.fetch_data", new_callable=AsyncMock) as mock: |
| 114 | mock.return_value = {"status": "ok"} |
| 115 | r = client.get("/dashboard") |
| 116 | assert r.status_code == 200 |
| 117 | mock.assert_called_once() |
| 118 | ``` |
| 119 | |
| 120 | ## Anti-Patterns |
| 121 | |
| 122 | | ❌ Don't | ✅ Do | |
| 123 | |----------|-------| |
| 124 | | Test against production | Use TestClient or local server | |
| 125 | | Hardcode auth tokens | Use fixtures or env vars | |
| 126 | | Skip error scenarios | Test 4xx/5xx responses explicitly | |
| 127 | | Ignore response body | Validate schema + data | |