Production-grade pytest test builder for FastAPI applications with SQLModel. This skill should be used when writing tests for API endpoints, business logic, database operations, or any Python code...
Writes production-grade tests using pytest for FastAPI applications with SQLModel databases.
pytest)Gather context to ensure tests match the codebase:
| Source | Gather |
|---|---|
| Code Under Test | Read the module/endpoint being tested to understand behavior |
| Codebase | Existing test structure (tests/ directory), fixture patterns in conftest.py, naming conventions, imports used |
| Conversation | User's specific testing requirements and edge cases |
| Skill References | Pytest patterns, FastAPI testing, database fixtures (in references/) |
| User Guidelines | Project-specific test conventions if documented |
Critical: ALWAYS read the code being tested before writing tests. Never write tests for code you haven't seen.
Before writing tests, determine:
Ask if unclear from context:
Note: Only ask questions that cannot be inferred from reading the code under test or existing test files.
This skill creates:
tests/test_<module>.py or tests/test_api_<resource>.pytests/conftest.py or local fixtures in test file1. Read Code Under Test
āā Understand function signatures, return types
āā Identify dependencies (database, external services)
āā Note edge cases and error conditions
2. Determine Test Type
āā API Endpoint Test ā Use TestClient + async patterns
āā Database Operation ā Use transaction rollback fixture
āā Business Logic ā Use pure unit test with mocks
āā Integration ā Combine multiple fixtures
3. Design Fixtures
āā Database session (if needed)
āā Test data (models, records)
āā Dependency overrides (for FastAPI)
āā Mocks (for external calls)
4. Write Test Cases
āā Happy path (expected behavior)
āā Edge cases (empty, null, boundaries)
āā Error cases (validation, not found, conflicts)
āā Use descriptive test names: test_<function>_<scenario>_<expected>
5. Apply Best Practices
āā One assertion concept per test (multiple asserts OK if related)
āā Use parametrize for similar cases
āā Add markers (@pytest.mark.asyncio, @pytest.mark.slow)
āā Keep tests readable and maintainable
tests/
āāā conftest.py # Shared fixtures (db session, client)
āāā test_<module>.py # Unit tests for modules
āāā test_api_<resource>.py # API endpoint tests
āāā test_integration_*.py # Integration tests
# Pattern: test_<function>_<scenario>_<expected>
def test_create_task_with_valid_data_returns_201(): ...
def test_get_task_when_not_found_returns_404(): ...
def test_update_task_without_auth_returns_403(): ...
| Scope | Use When | Example |
|---|---|---|
function |
Default - fresh state per test | Database transactions, test data |
class |
Shared setup for test class | Expensive setup shared in class |
module |
Once per test file | Database schema creation |
session |
Once per test run | Test database creation |
import pytest
from httpx import AsyncClient
@pytest.mark.asyncio
async def test_get_tasks_returns_all_tasks(async_client: AsyncClient, test_tasks):
"""Test that GET /tasks returns all tasks."""
response = await async_client.get("/tasks")
assert response.status_code == 200
data = response.json()
assert len(data) == len(test_tasks)
import pytest
from sqlmodel import Session, select
@pytest.mark.asyncio
async def test_create_task_persists_to_database(db_session: Session):
"""Test that creating a task saves to database."""
task = Task(title="Test", description="Test desc")
db_session.add(task)
db_session.commit()
db_session.refresh(task)
# Query to verify
result = db_session.exec(select(Task).where(Task.id == task.id)).first()
assert result is not None
assert result.title == "Test"
# Transaction will rollback after test
from unittest.mock import patch, MagicMock
def test_send_notification_calls_external_service(mock_http_client):
"""Test that send_notification calls the notification service."""
with patch('app.services.notifications.requests.post') as mock_post:
mock_post.return_value.status_code = 200
result = send_notification("user@example.com", "Hello")
assert result is True
mock_post.assert_called_once()
@pytest.mark.parametrize("input_data,expected_status", [
({"title": "Valid"}, 201),
({"title": ""}, 422), # Empty title
({"title": None}, 422), # Null title
({}, 422), # Missing title
])
async def test_create_task_validation(async_client, input_data, expected_status):
"""Test task creation with various inputs."""
response = await async_client.post("/tasks", json=input_data)
assert response.status_code == expected_status
Test Behavior, Not Implementation
Isolation
Clarity
Completeness
Maintainability
This skill uses patterns compatible with:
For latest patterns or version-specific features, consult official documentation linked in references.
For detailed patterns and examples, see:
| Reference | Content |
|---|---|
references/pytest-fixtures.md |
Fixture scopes, parametrization, autouse, built-ins |
references/fastapi-testing.md |
TestClient, async tests, dependency overrides |
references/database-testing.md |
SQLModel fixtures, transaction rollback, test data |
references/mocking-patterns.md |
unittest.mock, pytest-mock, patching strategies |
references/test-organization.md |
File structure, conftest.py, markers, running tests |
Before delivering tests, verify:
tests/test_*.py)test_<function>_<scenario>_<expected> pattern