Skip to content

Fixtures for testing

The recommended way to test modern-di applications with pytest is to install modern-di-pytest, which exposes any DI dependency as a pytest fixture. The plain-modern-di recipe at the bottom of this page still works and remains the right choice when you cannot add a test-only dependency.

uv add --dev modern-di-pytest
# 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(scope="session")
def di_container() -> typing.Iterator[modern_di.Container]:
    with modern_di.Container(groups=ioc.ALL_GROUPS) as container:
        yield container


@pytest.fixture
def di_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


# Bulk: every Provider on each group becomes a pytest fixture named after
# the class attribute. Pass several groups in one call if you split your
# providers across multiple Group subclasses.
expose(Dependencies)

# Manual: a single dependency as a named fixture.
email_client = modern_di_fixture(EmailClient)

Tests then receive resolved dependencies by name:

from app.services import EmailClient, SimpleFactory


def test_with_app_scope(simple_factory: SimpleFactory) -> None:
    # `simple_factory` was generated by expose(Dependencies)
    ...


def test_email(email_client: EmailClient) -> None:
    email_client.send("hi")

For overrides, use Container.override() directly — see the pytest integration page for the full pattern.

!!! note "Overrides are global" container.override() and container.reset_override() operate on the shared overrides registry, which is shared across all containers in the same tree (parent and all children). Calling override() on a child container affects every container in the tree for the duration of the override. Always call reset_override() in a finally block or use a fixture that guarantees cleanup.

2. Without modern-di-pytest — plain modern-di recipe

If you cannot add modern-di-pytest as a dev dependency, write the fixtures by hand. The example below is the equivalent of what modern-di-pytest would generate:

import typing

import modern_di
import modern_di_fastapi
import pytest
from fastapi import FastAPI

from app import ioc
from app.ioc import Dependencies, SimpleFactory


application = FastAPI()
modern_di_fastapi.setup_di(application, modern_di.Container(groups=ioc.ALL_GROUPS))


@pytest.fixture
async def di_container() -> typing.AsyncIterator[modern_di.Container]:
    async with modern_di_fastapi.fetch_di_container(application) as container:
        yield container


@pytest.fixture
async def request_di_container(
    di_container: modern_di.Container,
) -> typing.AsyncIterator[modern_di.Container]:
    async with di_container.build_child_container(scope=modern_di.Scope.REQUEST) as container:
        yield container


@pytest.fixture
def mock_dependencies(di_container: modern_di.Container) -> typing.Iterator[None]:
    di_container.override(
        provider=Dependencies.simple_factory,
        override_object=SimpleFactory(dep1="mock", dep2=777),
    )
    yield
    di_container.reset_override(Dependencies.simple_factory)

Use the fixtures in tests:

import pytest

from app.ioc import Dependencies


def test_with_app_scope(di_container: modern_di.Container) -> None:
    resource_instance = di_container.resolve_provider(Dependencies.sync_resource)
    # Do something with the dependency


def test_with_request_scope(request_di_container: modern_di.Container) -> None:
    simple_factory_instance = request_di_container.resolve_provider(Dependencies.simple_factory)
    # Do something with the dependency


@pytest.mark.usefixtures("mock_dependencies")
def test_with_request_scope_mocked(request_di_container: modern_di.Container) -> None:
    simple_factory_instance = request_di_container.resolve_provider(Dependencies.simple_factory)
    # The dependency is mocked here