Testing
Test Mode
When overriding dependencies in tests, AnyDI uses a special test mode that enables proper override support throughout the dependency graph. Test mode ensures that overridden dependencies are correctly propagated to all dependent services.
Enabling Test Mode
You can enable test mode in two ways:
Using the context manager (recommended):
with container.test_mode():
with container.override(Repository, mock_repo):
# All resolutions here will use the override
service = container.resolve(Service)
Using explicit methods:
container.enable_test_mode()
try:
with container.override(Repository, mock_repo):
service = container.resolve(Service)
finally:
container.disable_test_mode()
Why Test Mode Matters
AnyDI compiles optimized resolvers for fast dependency resolution. Test mode switches to a separate set of resolvers that include override checking logic. This ensures:
- Overrides are checked at every resolution point
- Nested dependencies correctly receive overridden instances
- No performance impact in production (normal resolvers have no override checks)
Note: If you use the pytest plugin (see below), test mode is automatically enabled for you.
Overriding Dependencies
Use the .override(dependency_type, instance) context manager to replace a dependency temporarily during testing. It helps you isolate code from its dependencies.
The container.override() works only inside the with block. When the block ends, the original dependency comes back.
from dataclasses import dataclass
from unittest import mock
from anydi import Container, Inject
@dataclass(kw_only=True)
class Item:
name: str
class Repository:
def __init__(self) -> None:
self.items: list[Item] = []
def all(self) -> list[Item]:
return self.items
class Service:
def __init__(self, repo: Repository) -> None:
self.repo = repo
def get_items(self) -> list[Item]:
return self.repo.all()
container = Container()
@container.inject
def get_items(service: Service = Inject()) -> list[Item]:
return service.get_items()
def test_handler() -> None:
repo_mock = mock.Mock(spec=Repository)
repo_mock.all.return_value = [Item(name="mock1"), Item(name="mock2")]
with container.test_mode():
with container.override(Repository, repo_mock):
assert get_items() == [Item(name="mock1"), Item(name="mock2")]
Or using nested context managers in a single with statement:
def test_handler() -> None:
repo_mock = mock.Mock(spec=Repository)
repo_mock.all.return_value = [Item(name="mock1"), Item(name="mock2")]
with container.test_mode(), container.override(Repository, repo_mock):
assert get_items() == [Item(name="mock1"), Item(name="mock2")]
Overriding Providers with Modules
You can create a testing module that overrides providers from your application modules. Use @provider(scope="...", override=True) to replace specific providers:
from anydi import Container, Module, provider
class Database:
def __init__(self, url: str) -> None:
self.url = url
class UserService:
def __init__(self, db: Database) -> None:
self.db = db
class AppModule(Module):
@provider(scope="singleton")
def database(self) -> Database:
return Database(url="postgresql://prod-server/db")
@provider(scope="singleton")
def user_service(self, db: Database) -> UserService:
return UserService(db=db)
class TestingModule(Module):
@provider(scope="singleton", override=True)
def database(self) -> Database:
return Database(url="sqlite:///:memory:")
# Setup container with both modules
container = Container()
container.register_module(AppModule())
container.register_module(TestingModule())
# UserService will use the overridden database from TestingModule
service = container.resolve(UserService)
assert service.db.url == "sqlite:///:memory:"
This approach is useful when you want to:
- Replace production dependencies with test doubles (e.g., in-memory databases)
- Override multiple providers in one place
- Share testing configuration across multiple test files
Pytest Plugin
AnyDI has a pytest plugin that injects dependencies into test functions. This makes tests simpler and cleaner.
Configuration
Container setup
You can provide a container for tests in two ways:
Option 1: Use the anydi_container configuration
Set the anydi_container option in your pytest.ini or pyproject.toml:
# pytest.ini
[pytest]
anydi_container = myapp.container:container
# pyproject.toml
[tool.pytest.ini_options]
anydi_container = "myapp.container:container"
The configuration accepts:
- Container instances:
myapp.container:containerormyapp.container.container - Factory functions:
myapp.container:create_container
# myapp/container.py
def create_container() -> Container:
container = Container()
# ... register providers
return container
Option 2: Define a container fixture
Or define a container fixture in your test suite (e.g., in conftest.py):
import pytest
from anydi import Container
from myapp import container as myapp_container
@pytest.fixture(scope="session")
def container() -> Container:
return myapp_container
Note: The fixture takes priority over the configuration if both are defined.
Usage
Explicit injection with Provide[T]
Use Provide[T] to mark parameters for injection from the container:
from anydi import Container, Provide
def test_service_get_items(
container: Container,
service: Provide[Service],
) -> None:
repo_mock = mock.Mock(spec=Repository)
repo_mock.all.return_value = [Item(name="mock1"), Item(name="mock2")]
with container.override(Repository, repo_mock):
assert service.get_items() == [Item(name="mock1"), Item(name="mock2")]
Dependencies are resolved from the container based on type annotations.
Auto-injection
Auto-injection is enabled by default. The plugin automatically injects any test parameter that matches a type registered in the container:
def test_service(service: Service) -> None:
assert service.get_items() == []
To disable auto-injection, set anydi_autoinject = false:
# pytest.ini
[pytest]
anydi_autoinject = false
# pyproject.toml
[tool.pytest.ini_options]
anydi_autoinject = false
Testing with .create()
For more control over dependency injection in tests, use the .create() method to instantiate classes with overridden dependencies:
def test_handler(container: Container) -> None:
repo_mock = mock.Mock(spec=Repository)
repo_mock.all.return_value = [Item(name="mock1"), Item(name="mock2")]
service = container.create(Service, repo=repo_mock)
assert service.get_items() == [Item(name="mock1"), Item(name="mock2")]