| name | pytest-backend-testing |
| description | Comprehensive pytest testing guide for FastAPI backends. Covers unit testing, integration testing, async patterns, mocking, fixtures, coverage, and FastAPI-specific testing with TestClient. Use when writing or updating test code for backend services, repositories, or API routes. |
| triggers | ["pytest","unit test","integration test","test coverage","mock","fixture","TestClient","async test","conftest","pytest-asyncio"] |
Pytest Backend Testing Guidelines
Purpose
Complete guide for writing comprehensive tests for FastAPI backend applications using pytest, pytest-asyncio, and FastAPI TestClient. Emphasizes async testing, proper mocking, layered testing (repository -> service -> router), and achieving high test coverage.
When to Use This Skill
- Writing new test files for backend code
- Testing repositories, services, or API routes
- Setting up test fixtures and mocks
- Debugging failing tests
- Improving test coverage
- Writing async tests with pytest-asyncio
- Testing database operations
- Using FastAPI TestClient for route testing
Quick Start
New Test File Checklist
Creating tests for new code? Follow this checklist:
Test Coverage Checklist
Ensuring good coverage? Check these:
Project Testing Structure
backend/
tests/
conftest.py # Global fixtures
unit/
domain/
{entity}/
test_{entity}_repository.py
test_{entity}_service.py
middleware/
test_error_handler.py
utils/
test_utils.py
integration/ # End-to-end tests
test_{entity}_api.py
test_auth_flow.py
Common Test Patterns Quick Reference
Basic Async Test
import pytest
from sqlmodel.ext.asyncio.session import AsyncSession
@pytest.mark.asyncio
async def test_get_entity_by_id(db_session: AsyncSession):
entity_id = "test-entity-id"
result = await repository.get_by_id(entity_id)
assert result is not None
assert result.id == entity_id
Mocking Database Session
from unittest.mock import AsyncMock, MagicMock
@pytest.mark.asyncio
async def test_create_entity_success():
mock_session = AsyncMock(spec=AsyncSession)
mock_session.execute = AsyncMock()
mock_session.commit = AsyncMock()
service = EntityService(mock_session)
result = await service.create_entity(data)
assert mock_session.commit.called
Testing FastAPI Routes
from fastapi.testclient import TestClient
from backend.main import create_application
@pytest.fixture
def client():
app = create_application()
return TestClient(app)
def test_get_entity_endpoint(client):
response = client.get("/api/v1/entities/test-id")
assert response.status_code == 200
assert response.json()["id"] == "test-id"
Test Organization Principles
Test Structure (AAA Pattern)
- Arrange: Set up test data, mocks, fixtures
- Act: Execute the code under test
- Assert: Verify the expected outcome
Test Naming Convention
def test_create_entity_with_valid_data_returns_entity()
def test_get_entity_when_not_found_raises_not_found_error()
def test_update_entity_with_duplicate_name_raises_conflict_error()
Test Organization
- Unit tests: Test individual functions/methods in isolation
- Integration tests: Test multiple components working together
- Group related tests: Use test classes for related functionality
Topic Guides
Testing Architecture
Three-Layer Testing Strategy:
- Repository Layer: Test database queries, CRUD operations
- Service Layer: Test business logic, orchestration
- Router Layer: Test API endpoints, request/response handling
Key Concepts:
- Mock dependencies at layer boundaries
- Test each layer independently
- Use integration tests for end-to-end flows
- Maintain test isolation
Complete Guide: resources/testing-architecture.md
Unit Testing
Unit Test Best Practices:
- Test single responsibility
- Mock external dependencies
- Fast execution (no database, no network)
- Independent and isolated
- Test both success and failure paths
@pytest.mark.asyncio
async def test_entity_service_create():
mock_repo = AsyncMock()
mock_repo.create = AsyncMock(return_value=entity_model)
service = EntityService(mock_repo)
result = await service.create_entity(data)
assert result.name == data.name
Complete Guide: resources/unit-testing.md
Integration Testing
Integration Test Focus:
- Test multiple components together
- Use real database (test database)
- Verify end-to-end workflows
- Test API contracts
@pytest.mark.asyncio
async def test_create_entity_flow(db_session, client):
response = client.post("/api/v1/entities", json=entity_data)
assert response.status_code == 201
entity = await db_session.get(Entity, response.json()["id"])
assert entity is not None
Complete Guide: resources/integration-testing.md
Async Testing
Async Test Patterns:
- Use
@pytest.mark.asyncio decorator
- Configure pytest-asyncio in conftest.py
- Mock async functions with AsyncMock
- Test async context managers
- Handle async exceptions
from unittest.mock import AsyncMock
@pytest.mark.asyncio
async def test_async_function():
mock_func = AsyncMock(return_value="result")
result = await mock_func()
assert result == "result"
mock_func.assert_awaited_once()
Complete Guide: resources/async-testing.md
Mocking & Fixtures
Mocking Strategy:
- Mock external dependencies (database, APIs, S3)
- Use pytest fixtures for reusable test data
- Mock at layer boundaries
- Use MagicMock for sync, AsyncMock for async
import pytest
@pytest.fixture
def sample_entity():
return Entity(
id="test-id",
name="Test Entity",
bio="Test bio"
)
@pytest.fixture
async def db_session():
async with get_test_session() as session:
yield session
await session.rollback()
Complete Guide: resources/mocking-fixtures.md
Coverage Best Practices
Coverage Strategy:
- Aim for 80%+ coverage (project requirement)
- Focus on critical business logic
- Test error paths and edge cases
- Use coverage reports to find gaps
pytest --cov=backend --cov-report=term-missing
pytest --cov=backend --cov-report=html
pytest --cov=backend --cov-fail-under=80
Complete Guide: resources/coverage-best-practices.md
FastAPI Testing
FastAPI Test Patterns:
- Use TestClient for route testing
- Test request validation
- Test response serialization
- Test authentication/authorization
- Test error handling middleware
from fastapi.testclient import TestClient
def test_create_entity_endpoint(client: TestClient):
response = client.post(
"/api/v1/entities",
json={"name": "Entity", "bio": "Bio"}
)
assert response.status_code == 201
data = response.json()
assert data["name"] == "Entity"
Complete Guide: resources/fastapi-testing.md
Core Principles
- Test Isolation: Each test runs independently, no shared state
- AAA Pattern: Arrange, Act, Assert for clear test structure
- Async Testing: Use pytest-asyncio for async code
- Mock Dependencies: Mock external systems (database, APIs)
- Layered Testing: Test each layer (repository, service, router) separately
- Coverage Goals: Aim for 80%+ coverage, focus on business logic
- Descriptive Names: Clear test names explain what, when, expected
- Error Testing: Test both success and failure paths
- Fast Tests: Unit tests should be fast (no real database)
- Fixtures: Use fixtures for reusable test data and setup
Anti-Patterns
1. ํ
์คํธ ๊ฐ ๊ณต์ ๊ฐ๋ณ ์ํ
- BAD: ๋ชจ๋ ๋ ๋ฒจ ๋ฆฌ์คํธ์ ํ
์คํธ ๋ฐ์ดํฐ ๋์
- GOOD: ๊ฐ ํ
์คํธ์์ fixture๋ก ๋
๋ฆฝ ๋ฐ์ดํฐ ์์ฑ
2. ๊ตฌํ ์ธ๋ถ์ฌํญ ํ
์คํธ
- BAD: private ๋ฉ์๋ ํธ์ถ ํ์ assert
- GOOD: public API์ ์
์ถ๋ ฅ ๊ฒฐ๊ณผ๋ง assert
3. ๋จ์ ํ
์คํธ์์ ์ค์ DB ์ฌ์ฉ
- BAD: ์ค์ PostgreSQL ์ธ์
์ผ๋ก unit test
- GOOD: AsyncMock ์ธ์
+ in-memory fixture
4. ์๋ฌ ๊ฒฝ๋ก ๋ฏธํ
์คํธ
- BAD: happy path๋ง ํ
์คํธ
- GOOD: 404, 401, 409, 500 ๋ฑ ์๋ฌ ์๋๋ฆฌ์ค ๋ณ๋ ํ
์คํธ
5. ๊ณผ๋ํ ๋ชจํน (over-mocking)
- BAD: ๋ชจ๋ ์์กด์ฑ์ Mock -- ์ค์ ๋์ ๋ฏธ๊ฒ์ฆ
- GOOD: Repository๋ง Mock, Service ๋ก์ง์ ์ค์ ์คํ
Quick Reference: Test Template
Complete template: resources/test-template.md
Related Skills
- fastapi-backend-guidelines: Backend development patterns (what you're testing)
- error-tracking: Error handling patterns to test
Skill Status: Modular structure with progressive loading for optimal context management