Skip to content

modern-python/modern-di-pytest

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

5 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

"Modern-DI-Pytest"

Supported versions downloads GitHub stars

Pytest integration for Modern-DI.

Turn any DI dependency into a pytest fixture with one line.

Install

uv add --dev modern-di-pytest

Usage

The user owns 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:
        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")

Pointing a fixture at a child container

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"
)

Overrides

Use Container.override() directly — modern-di already ships a first-class override mechanism backed by a tree-shared OverridesRegistry:

import modern_di

from app.ioc import Dependencies
from app.services import UserService
from tests.fakes import FakeRepo


def test_with_override(
    di_container: modern_di.Container,
    user_service: UserService,
) -> None:
    di_container.override(Dependencies.user_repo, FakeRepo())
    try:
        assert user_service.list_users() == []
    finally:
        di_container.reset_override(Dependencies.user_repo)

API

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 (resolved via container.resolve) or a Provider (resolved via container.resolve_provider). The returned object is a real pytest fixture — assign it to a module-level name and pytest will collect it.

expose(*groups, container_fixture="di_container", pytest_scope="function", module=None)

Walk each Group subclass in groups and inject one pytest fixture per Provider class attribute into 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. Pass module= explicitly when stack introspection cannot identify the caller.

About

Pytest integration for modern-di

Resources

License

Stars

Watchers

Forks

Contributors