# API Testing

> Test FastAPI endpoints with pytest and generate API documentation. Use when creating new APIs or verifying existing endpoints work correctly.

- Skill: `diegosouzapw/api-testing` (Agent Skill, multi-file: 2 files)
- Install (CLI): `npx skillmds@latest add diegosouzapw/api-testing`
- Raw SKILL.md: https://api.skillmd.com/api/skills/diegosouzapw/api-testing/raw
- Safety review: pending (external: skill-scanner PASS, skillspector PASS)
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Docs & Writing
- Author: diegosouzapw (https://skillmd.com/u/diegosouzapw)
- Updated: 2026-09-08
- Page: https://skillmd.com/skills/diegosouzapw/api-testing

---


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:

1. **Tests Run**: X passed, Y failed
2. **Coverage**: X% of code covered
3. **Failed Tests**: List with error messages
4. **Performance**: Average response time for key endpoints
5. **Issues Found**: Any bugs or unexpected behavior
6. **Recommendations**: Suggested improvements

## Best Practices

1. **Test pyramid**: More unit tests, fewer integration tests
2. **Independent tests**: Each test should be isolated
3. **Descriptive names**: `test_create_team_member_requires_admin_role`
4. **Use fixtures**: Share test data setup
5. **Test error cases**: Not just happy path
6. **Mock external services**: Don't depend on external APIs
7. **Fast tests**: Keep test suite under 1 minute if possible

