Testing with pytest =================== This project uses `pytest `_ for testing, along with `pytest-asyncio `_ for testing asynchronous FastAPI endpoints. Test Structure -------------- The test suite is organized in the ``tests/`` directory with the following structure: .. code-block:: tests/ ├── __init__.py ├── conftest.py # Test configuration and fixtures └── test_health.py # Health endpoint tests Test Configuration ------------------ The testing setup is configured in ``tests/conftest.py``, which provides: - **Async test client fixture** - Creates an HTTPX AsyncClient for testing FastAPI endpoints - **Automatic cleanup** - Clears dependency overrides after each test Key Components: .. literalinclude:: ../tests/conftest.py :language: python :lines: 6-12 Running Tests ------------- Docker Testing (Recommended) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Using Docker for testing ensures consistent test environments: .. code-block:: bash # Run all tests make test # Run tests with coverage report make test-cov # Run tests with Docker Compose directly docker compose -f docker-compose.dev.yml exec api-dev pytest # Run tests with verbose output docker compose -f docker-compose.dev.yml exec api-dev pytest -v # Run tests in a specific file docker compose -f docker-compose.dev.yml exec api-dev pytest tests/test_health.py # Run a specific test function docker compose -f docker-compose.dev.yml exec api-dev pytest tests/test_health.py::TestHealthEndpoint::test_health_endpoint_returns_200 Local Testing with Pipenv ~~~~~~~~~~~~~~~~~~~~~~~~~~ For local development setup: .. code-block:: bash # Activate pipenv environment pipenv shell # Run all tests pipenv run pytest # Run tests with verbose output pipenv run pytest -v # Run tests in a specific file pipenv run pytest tests/test_health.py # Run a specific test function pipenv run pytest tests/test_health.py::TestHealthEndpoint::test_health_endpoint_returns_200 Test Output and Coverage ~~~~~~~~~~~~~~~~~~~~~~~~ Docker testing with coverage: .. code-block:: bash # Run tests with coverage (generates HTML, XML, and terminal reports) make test-cov # View coverage report in browser (after running test-cov) # Open htmlcov/index.html in your browser Local testing with additional options: .. code-block:: bash # Show print statements during tests pipenv run pytest -s # Show detailed test results pipenv run pytest -v # Stop after first failure pipenv run pytest -x # Run tests matching a pattern pipenv run pytest -k "health" Async Testing ------------- This project uses ``pytest-asyncio`` to handle asynchronous tests. All async test functions should be marked with ``@pytest.mark.asyncio``: .. code-block:: python @pytest.mark.asyncio async def test_async_endpoint(self, client: AsyncClient): """Test an async endpoint.""" response = await client.get("/some-endpoint") assert response.status_code == 200 Writing Tests ------------- Test Class Organization ~~~~~~~~~~~~~~~~~~~~~~~ Tests are organized into classes that group related functionality: .. literalinclude:: ../tests/test_health.py :language: python :lines: 5-12 Using the Test Client ~~~~~~~~~~~~~~~~~~~~~ The ``client`` fixture provides an HTTPX AsyncClient configured for testing: .. code-block:: python @pytest.mark.asyncio async def test_api_endpoint(self, client: AsyncClient): # GET request response = await client.get("/api/endpoint") # POST request with JSON data response = await client.post("/api/create", json={"name": "test"}) # Request with headers response = await client.get("/api/protected", headers={"Authorization": "Bearer token"}) # Assertions assert response.status_code == 200 assert response.json() == {"expected": "result"} Test Examples ~~~~~~~~~~~~~ Here are common testing patterns for this FastAPI project: **Testing JSON responses:** .. code-block:: python @pytest.mark.asyncio async def test_json_response(self, client: AsyncClient): response = await client.get("/api/data") assert response.status_code == 200 data = response.json() assert "key" in data assert data["key"] == "expected_value" **Testing error responses:** .. code-block:: python @pytest.mark.asyncio async def test_not_found(self, client: AsyncClient): response = await client.get("/api/nonexistent") assert response.status_code == 404 **Testing POST endpoints:** .. code-block:: python @pytest.mark.asyncio async def test_create_resource(self, client: AsyncClient): payload = {"name": "test", "description": "Test resource"} response = await client.post("/api/resources", json=payload) assert response.status_code == 201 created = response.json() assert created["name"] == payload["name"] Best Practices -------------- Test Organization ~~~~~~~~~~~~~~~~~ - **Group related tests** into classes with descriptive names - **Use descriptive test method names** that explain what is being tested - **Add docstrings** to test classes and methods for documentation Test Data ~~~~~~~~~ - **Use fixtures** for reusable test data and setup - **Keep test data minimal** - only include what's necessary for the test - **Avoid hardcoded values** where possible - use variables or fixtures Assertions ~~~~~~~~~~ - **Test one thing per test** - each test should verify a single behavior - **Use specific assertions** - check exact values rather than just truthy/falsy - **Test both success and failure cases** for each endpoint Environment Isolation ~~~~~~~~~~~~~~~~~~~~~ - **Tests run in isolation** - the test client and fixtures ensure clean state - **Dependencies are automatically cleared** after each test via ``conftest.py`` - **Use dependency overrides** for mocking external services if needed Common Test Commands Summary ---------------------------- Docker Commands (Recommended) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. code-block:: bash # Basic testing make test # Run all tests make test-cov # Run tests with coverage report # Using Docker Compose directly docker compose -f docker-compose.dev.yml exec api-dev pytest # Run all tests docker compose -f docker-compose.dev.yml exec api-dev pytest -v # Verbose output docker compose -f docker-compose.dev.yml exec api-dev pytest -s # Show print statements docker compose -f docker-compose.dev.yml exec api-dev pytest -x # Stop on first failure # Specific test selection docker compose -f docker-compose.dev.yml exec api-dev pytest tests/test_health.py # Run specific file docker compose -f docker-compose.dev.yml exec api-dev pytest -k "health" # Run tests matching pattern docker compose -f docker-compose.dev.yml exec api-dev pytest tests/test_health.py::TestHealthEndpoint # Run specific class Local Commands (Pipenv) ~~~~~~~~~~~~~~~~~~~~~~~~ .. code-block:: bash # Basic testing pipenv run pytest # Run all tests pipenv run pytest -v # Verbose output pipenv run pytest -s # Show print statements pipenv run pytest -x # Stop on first failure # Specific test selection pipenv run pytest tests/test_health.py # Run specific file pipenv run pytest -k "health" # Run tests matching pattern pipenv run pytest tests/test_health.py::TestHealthEndpoint # Run specific class # Output and reporting pipenv run pytest --tb=short # Shorter traceback format pipenv run pytest --tb=no # No traceback pipenv run pytest -q # Quiet output