AgentSkillsCN

pytest-fastapi

使用 pytest 通过测试驱动开发(TDD)测试 FastAPI 应用程序。适用于为 FastAPI 端点编写测试、设置测试用例、测试异步路由、模拟依赖项、以回滚隔离方式进行数据库测试,或运行测试覆盖率时使用。可通过诸如“为我的 API 编写测试”、“添加单元测试”、“测试这个端点”、“设置 pytest”、“检查测试覆盖率”、“使用 TDD”、“先写测试”或“红-绿-重构”等请求触发。

SKILL.md
--- frontmatter
name: pytest-fastapi
description: Testing FastAPI applications with pytest using Test-Driven Development (TDD). Use when writing tests for FastAPI endpoints, setting up test fixtures, testing async routes, mocking dependencies, database testing with rollback isolation, or running test coverage. Triggers on requests like "write tests for my API", "add unit tests", "test this endpoint", "set up pytest", "check test coverage", "use TDD", "write tests first", or "red-green-refactor".

Pytest FastAPI Testing

Write and run tests for FastAPI applications using pytest, TestClient, and async testing patterns.

Quick Start

python
from fastapi.testclient import TestClient
from app.main import app

client = TestClient(app)

def test_read_root():
    response = client.get("/")
    assert response.status_code == 200
    assert response.json() == {"message": "Hello World"}

Setup

Install dependencies:

bash
pip install pytest pytest-asyncio httpx pytest-cov

Project structure:

code
project/
├── app/
│   ├── main.py
│   └── routers/
└── tests/
    ├── conftest.py    # Copy from assets/conftest.py
    └── test_*.py      # Copy pattern from assets/test_api_template.py

Core Patterns

Testing CRUD Endpoints

python
def test_create(client, sample_data):
    response = client.post("/items/", json=sample_data)
    assert response.status_code == 201
    assert "id" in response.json()

def test_read(client):
    response = client.get("/items/1")
    assert response.status_code == 200

def test_not_found(client):
    response = client.get("/items/99999")
    assert response.status_code == 404

Testing with Authentication

python
def test_protected_no_auth(client):
    response = client.get("/protected/")
    assert response.status_code == 401

def test_protected_with_auth(client, auth_headers):
    response = client.get("/protected/", headers=auth_headers)
    assert response.status_code == 200

Testing Validation

python
def test_invalid_input(client):
    response = client.post("/items/", json={"invalid": "data"})
    assert response.status_code == 422

Async Endpoint Testing

python
@pytest.mark.asyncio
async def test_async_endpoint(async_client):
    response = await async_client.get("/async-data")
    assert response.status_code == 200

Dependency Override

python
def test_with_mock_service(client):
    def mock_service():
        return MockService()

    app.dependency_overrides[get_service] = mock_service
    response = client.get("/service-endpoint")
    app.dependency_overrides.clear()
    assert response.status_code == 200

TDD Workflow

Follow the Red-Green-Refactor cycle:

1. Red: Write a Failing Test First

python
def test_calculate_discount():
    # Write test for feature that doesn't exist yet
    result = calculate_discount(price=100, percent=20)
    assert result == 80.0  # This will fail - function doesn't exist

2. Green: Write Minimal Code to Pass

python
def calculate_discount(price: float, percent: float) -> float:
    return price - (price * percent / 100)

3. Refactor: Improve While Tests Pass

python
def calculate_discount(price: float, percent: float) -> float:
    """Apply percentage discount to price."""
    if not 0 <= percent <= 100:
        raise ValueError("Percent must be between 0 and 100")
    return round(price * (1 - percent / 100), 2)

TDD for FastAPI Endpoints

python
# Step 1: Write failing test for new endpoint
def test_search_items(client):
    response = client.get("/items/search?q=test")
    assert response.status_code == 200
    assert "results" in response.json()

# Step 2: Implement minimal endpoint
@app.get("/items/search")
def search_items(q: str):
    return {"results": []}

# Step 3: Refactor with actual search logic

Watch Mode for Rapid Feedback

bash
# Install pytest-watch for auto-rerun on file changes
pip install pytest-watch

# Run in watch mode (re-runs tests on save)
ptw

# Watch specific directory
ptw tests/

# With verbose output
ptw -- -v

Running Tests

bash
# Run all tests
pytest

# Run with verbose output
pytest -v

# Run specific file
pytest tests/test_items.py

# Run with coverage
pytest --cov=app --cov-report=html

# Run async tests
pytest -v --asyncio-mode=auto

Resources

Templates (assets/)

  • conftest.py - Ready-to-use fixtures for TestClient, async client, database session, auth headers
  • test_api_template.py - Example test patterns for CRUD, validation, auth, and async testing

Copy templates to your tests/ directory and customize for your app.

Reference Documentation (references/)

See api_reference.md for:

  • Complete fixture patterns and scopes
  • Database testing with transaction rollback
  • Mocking strategies
  • Coverage configuration

See tdd_patterns.md for:

  • Detailed red-green-refactor examples
  • TDD workflow commands and watch mode
  • Common TDD patterns (Arrange-Act-Assert, factory fixtures)
  • Anti-patterns to avoid