Pytest integration for modern-di. It turns any DI dependency into a pytest fixture with one line.
Full guide: pytest integration docs
uv add --dev modern-di-pytest # or: pip install modern-di-pytestYou provide the root container fixture. Pick whatever pytest scope fits the test suite:
# conftest.py
import typing
import modern_di
import pytest
from modern_di_pytest import expose, modern_di_fixture
from app import ioc
from app.ioc import Dependencies
from app.services import EmailClient
@pytest.fixture
def di_container() -> typing.Iterator[modern_di.Container]:
with modern_di.Container(groups=ioc.ALL_GROUPS) as container:
container.validate() # optional fail-fast; nothing validates the graph implicitly
yield container
# Bulk: every Provider on each group becomes a pytest fixture
# named after the class attribute. Pass several groups in one call.
expose(Dependencies)
# Manual: turn a single type or Provider into a named fixture.
email_client = modern_di_fixture(EmailClient)Tests then receive resolved dependencies by name:
from app.services import EmailClient, UserService
def test_listing(user_service: UserService) -> None: # generated by expose(Dependencies)
assert user_service.list_users() == []
def test_email(email_client: EmailClient) -> None: # generated manually
email_client.send("hi")import typing
import modern_di
import pytest
from modern_di_pytest import modern_di_fixture
from app.services import UserService
@pytest.fixture
def request_container(
di_container: modern_di.Container,
) -> typing.Iterator[modern_di.Container]:
with di_container.build_child_container(scope=modern_di.Scope.REQUEST) as container:
yield container
request_user_service = modern_di_fixture(UserService, container_fixture="request_container")Use Container.override(); overrides are shared across the container tree.
Generated fixtures resolve during test setup, so apply the override in a fixture
they depend on. Overriding di_container in the test module does that:
import typing
import modern_di
import pytest
from app.ioc import Dependencies
from app.services import UserService
from tests.fakes import FakeRepo
@pytest.fixture
def di_container(di_container: modern_di.Container) -> typing.Iterator[modern_di.Container]:
with di_container.override(Dependencies.user_repo, FakeRepo()):
yield di_container
def test_with_override(user_service: UserService) -> None:
assert user_service.list_users() == []modern_di_fixture(dependency, *, container_fixture="di_container", name=None, pytest_scope="function")
Turn a single dependency into a pytest fixture. dependency is either a
type or a Provider; the generated fixture resolves it through
container.resolve_dependency. The returned object is a real pytest fixture;
assign it to a module-level name and pytest will collect it.
Walk each Group subclass in groups and install one pytest fixture per
Provider class attribute onto the caller's module. Fixture names equal the
class-attribute names. Non-Provider class attributes are skipped. A duplicate
attribute name across groups raises ValueError, and calling it with no
groups raises TypeError. Pass module= explicitly when stack
introspection cannot identify the caller.
π¦ PyPI
π License
Built on modern-di, a dependency-injection framework with an IoC container and scopes.
Browse the full list of templates and libraries in
modern-python; the org profile has the categorized index.