Comprehensive unit tests for the FastAPI Example application covering domain entities, services, handlers, and infrastructure components.
test/
└── unit/
├── conftest.py # Global fixtures and configuration
├── domain/
│ └── test_user_entity.py # Domain entity tests (5 tests)
├── application/
│ ├── services/
│ │ ├── test_token_jwt_service.py # JWT token service tests (9 tests)
│ │ └── test_rate_limiter_service.py # Rate limiter service tests (5 tests)
│ └── features/
│ ├── auth/
│ │ └── test_create_access_token_handler.py # Auth handler tests (6 tests)
│ └── users/
│ ├── test_create_user_handler.py # User creation handler tests (5 tests)
│ ├── test_get_user_handler.py # User retrieval handler tests (5 tests)
│ ├── test_update_user_handler.py # User update handler tests (7 tests)
│ └── test_delete_user_handler.py # User deletion handler tests (6 tests)
└── infrastructure/
└── test_argon_hasher.py # Password hasher tests (9 tests)
Total: 55 tests across 9 test files
Install dependencies with test extras and run all tests:
uv sync --all-extras
uv run pytest test/unit -v# Domain tests
uv run pytest test/unit/domain -v
# Application layer tests
uv run pytest test/unit/application -v
# Service tests only
uv run pytest test/unit/application/services -v
# Feature handler tests
uv run pytest test/unit/application/features -v
# User handlers
uv run pytest test/unit/application/features/users -v
# Auth handlers
uv run pytest test/unit/application/features/auth -v
# Infrastructure tests
uv run pytest test/unit/infrastructure -vuv run pytest test/unit/application/features/users/test_create_user_handler.py -vuv run pytest test/unit/application/features/users/test_create_user_handler.py::TestCreateUserHandler::test_create_user_success -vuv run pytest test/unit --cov=src/fastapi_example --cov-report=htmlThe HTML report will be generated in the htmlcov/ directory.
# Stop on first failure
uv run pytest test/unit -x
# Show local variables in tracebacks
uv run pytest test/unit -l
# Run with minimal output
uv run pytest test/unit -q
# Show slowest tests
uv run pytest test/unit --durations=10Current Status: 55 tests, all passing ✅
Coverage: 42% (983 statements analyzed, 572 not covered)
Core modules with high coverage:
src/fastapi_example/domain/entities/- 100%src/fastapi_example/application/services/- 100%src/fastapi_example/infrastructure/security/- 100%src/fastapi_example/infrastructure/cache/- Covered by service testssrc/fastapi_example/application/features/- 40-100%
File: test/unit/domain/test_user_entity.py
Tests for the User domain entity:
- test_create_user_with_all_fields - Verify user creation with all fields
- test_create_user_with_none_timestamps - Verify user creation with null timestamps
- test_user_dataclass_equality - Verify dataclass equality comparison
- test_user_different_users_not_equal - Verify different users are not equal
- test_user_with_special_characters - Verify support for special characters in username and email
File: test/unit/application/features/auth/test_create_access_token_handler.py
Tests for authentication token creation:
- test_create_access_token_success - Verify successful token generation
- test_create_access_token_user_not_found - Verify error when user not found
- test_create_access_token_wrong_password - Verify error on invalid password
- test_create_access_token_with_scopes - Verify token creation with scopes
- test_create_access_token_verifies_password - Verify password verification is called
- test_create_access_token_empty_username - Verify error on empty username
File: test/unit/application/services/test_token_jwt_service.py
Tests for JWT token creation and verification:
- test_create_access_token_success - Verify successful token creation
- test_create_access_token_with_scopes - Verify token creation with scopes
- test_verify_token_success - Verify successful token verification
- test_verify_token_invalid - Verify rejection of invalid tokens
- test_verify_token_expired - Verify rejection of expired tokens
- test_verify_token_missing_subject - Verify rejection of tokens without subject
- test_get_user_id_from_token_success - Verify extraction of user ID from token
- test_get_user_id_from_token_invalid - Verify error handling for invalid tokens
- test_get_user_id_from_token_invalid_uuid_format - Verify error on invalid UUID format
File: test/unit/application/services/test_rate_limiter_service.py
Tests for request rate limiting:
- test_check_within_limit - Verify requests within limit are allowed
- test_check_at_limit - Verify requests at limit boundary are allowed
- test_check_exceeds_limit - Verify requests exceeding limit are rejected
- test_check_zero_requests - Verify first request is allowed
- test_check_creates_correct_key - Verify cache key is formatted correctly
File: test/unit/application/features/users/test_create_user_handler.py
Tests for user creation command handler:
- test_create_user_success - Verify successful user creation
- test_create_user_already_exists - Verify error when user already exists
- test_create_user_hashes_password - Verify password is hashed before storage
- test_create_user_checks_username_and_email - Verify both username and email are checked
- test_create_user_with_special_characters - Verify support for special characters
File: test/unit/application/features/users/test_get_user_handler.py
Tests for user retrieval query handler:
- test_get_user_success - Verify successful user retrieval by ID
- test_get_user_not_found - Verify error when user not found
- test_get_user_with_timestamps - Verify timestamps are returned correctly
- test_get_user_with_special_characters - Verify special characters in username/email
- test_get_user_calls_transaction_manager - Verify transaction context is used
File: test/unit/application/features/users/test_update_user_handler.py
Tests for user update command handler:
- test_update_user_success - Verify successful user update
- test_update_user_not_found - Verify error when user not found
- test_update_user_username_already_exists - Verify conflict on duplicate username
- test_update_user_email_already_exists - Verify conflict on duplicate email
- test_update_user_partial - Verify partial updates (only some fields)
- test_update_user_same_username - Verify updating with same username doesn't check exists
- test_update_user_uses_for_update - Verify row is locked for update during transaction
File: test/unit/application/features/users/test_delete_user_handler.py
Tests for user deletion command handler:
- test_delete_user_success - Verify successful user deletion
- test_delete_user_not_found - Verify error when user not found
- test_delete_user_wrong_password - Verify error on wrong password confirmation
- test_delete_user_verifies_password - Verify password verification is called
- test_delete_user_empty_password - Verify error on empty password
- test_delete_user_requires_for_update - Verify row is locked during deletion
File: test/unit/infrastructure/test_argon_hasher.py
Tests for Argon2 password hashing:
- test_hash_password - Verify password hashing creates a hash
- test_hash_password_different_hashes - Verify same password produces different hashes (salt)
- test_verify_password_success - Verify successful password verification
- test_verify_password_failure - Verify rejection of wrong password
- test_verify_password_case_sensitive - Verify password verification is case-sensitive
- test_verify_password_empty_password - Verify empty password fails verification
- test_hash_empty_password - Verify empty password can be hashed
- test_hash_long_password - Verify long passwords are handled correctly
- test_hash_special_characters - Verify special characters in passwords are handled
Global fixtures defined in test/unit/conftest.py:
Provides JWT settings with test RSA keys for token generation/verification.
@pytest.fixture
def jwt_settings() -> JWTSettings:
return JWTSettings(
algorithm="RS256",
expiration=30,
)Provides an async context manager mock for database transactions.
@pytest.fixture
def mock_transaction_manager():
mock = MagicMock()
mock.__aenter__ = AsyncMock(return_value=mock)
mock.__aexit__ = AsyncMock(return_value=None)
return mockProvides an async mock for the users repository.
@pytest.fixture
def mock_users_repository():
return AsyncMock()Provides an async mock for the cache repository.
@pytest.fixture
def mock_cache_repository():
return AsyncMock()Provides a mock for password hasher with default implementations.
@pytest.fixture
def mock_hasher():
mock = MagicMock()
mock.hash_password = MagicMock(return_value="hashed_password")
mock.verify_password = MagicMock(return_value=True)
return mock- Use descriptive names that explain what is being tested
- Follow the pattern:
test_<function>_<scenario> - Example:
test_create_access_token_wrong_password
Tests follow the AAA pattern:
# Arrange - Set up test data and mocks
user_id = uuid4()
cmd = CreateUserCommand(...)
# Act - Execute the code being tested
result = await handler.execute(cmd)
# Assert - Verify the results
assert result.username == cmd.username- Mock external dependencies (repositories, services)
- Use real implementations for domain logic when possible
- Mock async functions with
AsyncMock - Mock regular functions with
MagicMock
- Each test is independent and can run in any order
- No shared state between tests
- Use fixtures for common setup
- Clean data in teardown if needed
- Mark async tests with
@pytest.mark.asyncio - Use
async deffor async test functions - Use
awaitfor async operations
- One logical assertion per test when possible
- Use context managers for exception testing:
with pytest.raises(ExceptionType) - Check both positive and negative cases
- Assertions include clear error messages
- Use
matchparameter inpytest.raisesfor specific error messages - Example:
pytest.raises(UnAuthorizedError, match="Incorrect login")
Configuration in pytest.ini:
- testpaths:
test/unit- Where to find tests - python_files:
test_*.py- Test file naming pattern - python_classes:
Test*- Test class naming pattern - python_functions:
test_*- Test function naming pattern - asyncio_mode:
auto- Automatic asyncio mode for async tests - Coverage options: Generate HTML reports and show missing lines
- Markers: Define async, unit, and integration test markers
Test dependencies are installed via uv sync --all-extras:
- pytest (8.4.2) - Testing framework
- pytest-asyncio (0.24.0) - Async test support
- pytest-cov (5.0.0) - Code coverage analysis
- unittest.mock (built-in) - Mocking framework
Tests are designed to run in CI/CD pipelines:
# Install dependencies
uv sync --all-extras
# Run tests with coverage
uv run pytest test/unit --cov=src/fastapi_example --cov-report=term-missingEnsure pytest can find the tests:
uv run pytest test/unit --collect-onlyVerify the package is installed:
uv run pytest -v --tb=shortEnsure @pytest.mark.asyncio is on async test functions and asyncio_mode=auto in pytest.ini.
Verify mock is assigned before the test execution, not after.