Skip to content

Testing Strategy

Detailed testing patterns, organization, and requirements for the project.

Coverage Requirements

100% coverage required on production code.

Included:
- All code in src/ except explicitly excluded files (see below)

Excluded:
- server.py (FastAPI entrypoint - validate server behavior with integration tests)
- **/agent.py (pure ADK configuration - tests are the upstream project's responsibility)
- **/prompt.py (prompt templates - validate agent behavior with evaluations)
- **/__init__.py (module initialization)

Test Organization

Directory Structure

Tests mirror source structure:

tests/
  conftest.py                    # Shared fixtures, mocks, and test environment setup
  test_callbacks.py              # Tests for src/agent_foundation/callbacks.py
  test_tools.py                  # Tests for src/agent_foundation/tools.py
  ...

Naming Conventions

  • Files: test_<module>.py mirroring source module name
  • Functions: test_<what>_<condition>_<expected>
  • Classes: Group related tests for the same module/class (style preference)

Shared Fixtures

All reusable fixtures go in tests/conftest.py:
- Type hint fixture definitions with both parameters and return types
- Use pytest-mock type aliases for returns: MockType, AsyncMockType
- Factory pattern (not context managers)

Fixture Patterns

Type Hints

Fixture definitions (strict in conftest.py):

from pytest_mock import MockerFixture
from pytest_mock.plugin import MockType

@pytest.fixture
def mock_session(mocker: MockerFixture) -> MockType:
    """Create a mock ADK session."""
    return mocker.MagicMock(spec=...)

Test functions (relaxed):
- Don't type hint custom fixtures (pytest handles DI by name)
- Optional type hints on built-in fixtures for IDE support

Environment Mocking

No base env vars in pytest_configure(): No module in the test import graph reads env vars at module level. server.py does (initialize_environment at line 26), but it's never imported during collection (PEP 562 lazy loading, coverage-excluded). Environment variables are set in test fixtures using mocker.patch.dict:

def test_config_with_custom_region(mocker: MockerFixture) -> None:
    """Test configuration with non-default region."""
    mocker.patch.dict(os.environ, {"GOOGLE_CLOUD_LOCATION": "us-west1"})
    # Test config loading with custom region

If a future import chain triggers env var reads at collection time, add direct os.environ assignments in pytest_configure() (see section below).

ADK Mock Strategy

Using Conftest Fixtures

Prefer fixtures from conftest.py for standard mocks:
- mock_state - ADK state object
- mock_session - ADK session
- mock_context - ReadonlyContext with user_id

Never Import Mock Classes

Never import mock classes directly in test files. Always use or add a fixture in conftest.py. For edge cases requiring custom internal structure, add a specific named fixture (e.g., mock_event_non_text_content) rather than importing the class.

Mirror Real Interfaces

ADK mocks must exactly mirror real ADK interfaces:
- Use spec= parameter to enforce interface
- Include all properties and methods used in code
- Match return types and signatures

pytest_configure()

Special case: unittest.mock before pytest-mock available

pytest_configure() runs before test collection, so pytest-mock isn't available yet.
Use unittest.mock here for setup that must happen before tests load:

def pytest_configure() -> None:
    """Configure test environment before test collection."""
    from unittest.mock import Mock, patch

    # Patch load_dotenv to prevent loading real .env
    patch("dotenv.load_dotenv").start()

    # Patch google.auth.default to prevent ADC lookup
    mock_creds = Mock(token="fake", valid=True, expired=False)
    patch("google.auth.default", return_value=(mock_creds, "test-project")).start()
    patch("google.auth._default.default", return_value=(mock_creds, "test-project")).start()

    # Environment variables: No module in the test import graph reads env vars at
    # module level. server.py does (initialize_environment(ServerEnv)), but it's
    # never imported during collection (PEP 562 lazy loading, coverage-excluded).
    # If a future import chain triggers env var reads at collection time, set
    # defaults here using direct assignment:
    # import os
    # os.environ["KEY"] = "value"

See tests/conftest.py for the complete implementation with detailed lifecycle comments.

Pydantic Validation Testing

Validation Timing

Pydantic validates at model creation, not at property access:

# Validation happens here ✓
config = ServerEnv.model_validate(env_dict)

# NOT here ✗
value = config.some_property

Testing Validation

Expect ValidationError at model creation:

import pytest
from pydantic import ValidationError

def test_config_invalid_project_id():
    """Test that empty project ID raises validation error."""
    env_dict = {"GOOGLE_CLOUD_PROJECT": ""}

    with pytest.raises(ValidationError):
        ServerEnv.model_validate(env_dict)

What to Test

Behaviors

Test function outputs and state changes:
- Return values are correct
- State is modified as expected
- Side effects occur (logging, callbacks)

Error Conditions

Test invalid inputs and edge cases:
- Invalid parameter types
- Missing required values
- Boundary conditions (empty, max, negative)

Integration Points

Test connections between components:
- Callbacks are registered and invoked
- Tools are added to agent correctly
- Configuration flows through system

Mypy Override for Tests

Tests use relaxed type checking for pragmatism:

[[tool.mypy.overrides]]
module = "tests.*"
disable_error_code = ["arg-type"]

Still type hint conftest.py fully, but test functions can be more flexible.

Running Tests

# Full coverage report
uv run pytest --cov --cov-report=term-missing

# HTML report for detailed view
uv run pytest --cov --cov-report=html
open htmlcov/index.html

# Specific tests
uv run pytest tests/test_integration.py -v
uv run pytest tests/test_file.py::test_name -v

# Watch mode (requires pytest-watch)
uv run ptw

Examples

See tests/conftest.py and existing test files for complete patterns:
- Fixture factories
- ADK mocks
- Environment mocking
- Pydantic validation tests
- Async test patterns (with pytest-asyncio)


Back to References | Documentation