Agent skill
api-testing
Test FastAPI endpoints with pytest and generate API documentation. Use when creating new APIs or verifying existing endpoints work correctly.
Stars
163
Forks
31
Install this agent skill to your Project
npx add-skill https://github.com/majiayu000/claude-skill-registry/tree/main/skills/data/api-testing
SKILL.md
You help test FastAPI endpoints for the QA Team Portal backend using pytest and manual testing tools.
When to Use This Skill
- Testing new API endpoints after creation
- Verifying authentication/authorization works
- Testing CRUD operations
- Checking error handling and validation
- Load/stress testing APIs
- Generating API documentation examples
Testing Approaches
1. Automated Testing with Pytest
Unit Tests (Fast, Isolated)
python
# tests/unit/test_team_service.py
import pytest
from app.services.team_service import TeamService
def test_validate_team_member_data():
service = TeamService()
data = {"name": "John Doe", "role": "QA Lead"}
assert service.validate(data) is True
def test_validate_rejects_invalid_email():
service = TeamService()
data = {"name": "John", "email": "invalid"}
with pytest.raises(ValueError):
service.validate(data)
Integration Tests (Full API Flow)
python
# tests/integration/test_api_team_members.py
import pytest
from fastapi.testclient import TestClient
from app.main import app
client = TestClient(app)
def test_get_team_members():
response = client.get("/api/v1/team-members")
assert response.status_code == 200
assert isinstance(response.json(), list)
def test_create_team_member_requires_auth():
data = {"name": "John Doe", "role": "QA Lead"}
response = client.post("/api/v1/team-members", json=data)
assert response.status_code == 401
def test_create_team_member_with_auth(admin_token):
headers = {"Authorization": f"Bearer {admin_token}"}
data = {
"name": "John Doe",
"role": "QA Lead",
"email": "john@example.com"
}
response = client.post("/api/v1/team-members", json=data, headers=headers)
assert response.status_code == 201
assert response.json()["name"] == "John Doe"
def test_update_team_member(admin_token, test_team_member):
headers = {"Authorization": f"Bearer {admin_token}"}
data = {"name": "Jane Doe"}
response = client.put(
f"/api/v1/team-members/{test_team_member.id}",
json=data,
headers=headers
)
assert response.status_code == 200
assert response.json()["name"] == "Jane Doe"
def test_delete_team_member(admin_token, test_team_member):
headers = {"Authorization": f"Bearer {admin_token}"}
response = client.delete(
f"/api/v1/team-members/{test_team_member.id}",
headers=headers
)
assert response.status_code == 204
Pytest Fixtures
python
# tests/conftest.py
import pytest
from fastapi.testclient import TestClient
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from app.main import app
from app.db.base import Base
from app.api.deps import get_db
# Test database
SQLALCHEMY_DATABASE_URL = "sqlite:///./test.db"
engine = create_engine(SQLALCHEMY_DATABASE_URL)
TestingSessionLocal = sessionmaker(bind=engine)
@pytest.fixture(scope="function")
def db():
Base.metadata.create_all(bind=engine)
db = TestingSessionLocal()
try:
yield db
finally:
db.close()
Base.metadata.drop_all(bind=engine)
@pytest.fixture
def client(db):
def override_get_db():
try:
yield db
finally:
db.close()
app.dependency_overrides[get_db] = override_get_db
return TestClient(app)
@pytest.fixture
def admin_token(client):
response = client.post("/api/v1/auth/login", json={
"email": "admin@test.com",
"password": "testpass123"
})
return response.json()["access_token"]
@pytest.fixture
def test_team_member(db):
from app.models.team_member import TeamMember
member = TeamMember(
name="Test User",
role="QA Engineer",
email="test@test.com"
)
db.add(member)
db.commit()
db.refresh(member)
return member
2. Manual Testing with curl
bash
# Health check
curl http://localhost:8000/health
# Get all team members (public)
curl http://localhost:8000/api/v1/team-members
# Login
TOKEN=$(curl -X POST http://localhost:8000/api/v1/auth/login \
-H "Content-Type: application/json" \
-d '{"email":"admin@test.com","password":"pass"}' \
| jq -r '.access_token')
# Create team member (admin)
curl -X POST http://localhost:8000/api/v1/team-members \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "John Doe",
"role": "QA Lead",
"email": "john@example.com"
}'
# Upload profile photo
curl -X POST http://localhost:8000/api/v1/team-members/123/photo \
-H "Authorization: Bearer $TOKEN" \
-F "file=@profile.jpg"
# Get with filters
curl "http://localhost:8000/api/v1/team-members?role=QA%20Lead&active=true"
3. Testing with HTTPie (Prettier Output)
bash
# Install httpie
pip install httpie
# Login
http POST localhost:8000/api/v1/auth/login email=admin@test.com password=pass
# Create with auth
http POST localhost:8000/api/v1/team-members \
Authorization:"Bearer $TOKEN" \
name="John Doe" \
role="QA Lead" \
email="john@example.com"
# Pretty print JSON
http GET localhost:8000/api/v1/team-members | jq '.'
Running Tests
bash
cd backend
# Run all tests
uv run pytest
# Run with verbose output
uv run pytest -v
# Run specific test file
uv run pytest tests/integration/test_api_team_members.py
# Run specific test
uv run pytest tests/integration/test_api_team_members.py::test_create_team_member
# Run with coverage
uv run pytest --cov=app --cov-report=html
# Run only integration tests
uv run pytest tests/integration/
# Show print statements
uv run pytest -s
# Stop on first failure
uv run pytest -x
# Run tests matching pattern
uv run pytest -k "team_member"
Test Coverage
bash
# Generate coverage report
uv run pytest --cov=app --cov-report=term-missing
# Generate HTML report
uv run pytest --cov=app --cov-report=html
open htmlcov/index.html
# Coverage for specific module
uv run pytest --cov=app.api.v1.endpoints --cov-report=term
Load Testing
bash
# Install locust
uv pip install locust
# Create locustfile.py
cat > locustfile.py <<'EOF'
from locust import HttpUser, task, between
class APIUser(HttpUser):
wait_time = between(1, 3)
@task
def get_team_members(self):
self.client.get("/api/v1/team-members")
@task(3)
def get_updates(self):
self.client.get("/api/v1/updates")
EOF
# Run load test
uv run locust -f locustfile.py --host=http://localhost:8000
# Or headless mode
uv run locust -f locustfile.py --host=http://localhost:8000 \
--users 100 --spawn-rate 10 --run-time 1m --headless
API Documentation Testing
bash
# Access interactive docs
open http://localhost:8000/api/v1/docs
# Get OpenAPI schema
curl http://localhost:8000/api/v1/openapi.json | jq '.' > openapi.json
# Validate OpenAPI schema
npx @stoplight/spectral-cli lint openapi.json
Test Checklist
For each endpoint, verify:
- Success cases - Returns 200/201/204 as expected
- Authentication - Returns 401 without token
- Authorization - Returns 403 for insufficient permissions
- Validation - Returns 422 for invalid data
- Not Found - Returns 404 for non-existent resources
- Edge cases - Empty lists, null values, boundary conditions
- Error handling - Doesn't expose sensitive info in errors
- Rate limiting - Enforced on sensitive endpoints
- CORS - Allows configured origins only
- Response format - Matches schema definition
Common Test Patterns
Testing Authentication
python
def test_endpoint_requires_authentication(client):
response = client.post("/api/v1/admin/users")
assert response.status_code == 401
def test_endpoint_rejects_expired_token(client, expired_token):
headers = {"Authorization": f"Bearer {expired_token}"}
response = client.get("/api/v1/admin/users", headers=headers)
assert response.status_code == 401
Testing Validation
python
def test_rejects_invalid_email(client, admin_token):
headers = {"Authorization": f"Bearer {admin_token}"}
data = {"name": "John", "email": "invalid"}
response = client.post("/api/v1/team-members", json=data, headers=headers)
assert response.status_code == 422
assert "email" in response.json()["detail"][0]["loc"]
Testing Pagination
python
def test_pagination_limits_results(client):
response = client.get("/api/v1/team-members?limit=5")
assert len(response.json()) <= 5
def test_pagination_skip_offset(client):
response1 = client.get("/api/v1/team-members?skip=0&limit=2")
response2 = client.get("/api/v1/team-members?skip=2&limit=2")
assert response1.json()[0]["id"] != response2.json()[0]["id"]
Output Format
After testing, report:
- Tests Run: X passed, Y failed
- Coverage: X% of code covered
- Failed Tests: List with error messages
- Performance: Average response time for key endpoints
- Issues Found: Any bugs or unexpected behavior
- Recommendations: Suggested improvements
Best Practices
- Test pyramid: More unit tests, fewer integration tests
- Independent tests: Each test should be isolated
- Descriptive names:
test_create_team_member_requires_admin_role - Use fixtures: Share test data setup
- Test error cases: Not just happy path
- Mock external services: Don't depend on external APIs
- Fast tests: Keep test suite under 1 minute if possible
Didn't find tool you were looking for?