diff --git a/modern_di_pytest/factory.py b/modern_di_pytest/factory.py index 359e9f1..5cc646a 100644 --- a/modern_di_pytest/factory.py +++ b/modern_di_pytest/factory.py @@ -57,9 +57,9 @@ def _fixture(request: pytest.FixtureRequest) -> typing.Any: # noqa: ANN401 def _collect_fixtures(*groups: type[Group]) -> dict[str, AbstractProvider[typing.Any]]: """Decide which Providers to expose and under what names. - Pure: walks each group's attributes, keeps the ``AbstractProvider``s, skips - everything else, and returns a ``name -> provider`` mapping. Raises before - returning anything so callers never act on a partial result: + Pure: asks each group for its named providers and returns a + ``name -> provider`` mapping. Raises before returning anything so callers + never act on a partial result: - ``TypeError`` if no groups are given. - ``ValueError`` if a name is claimed by more than one group. @@ -71,9 +71,7 @@ def _collect_fixtures(*groups: type[Group]) -> dict[str, AbstractProvider[typing providers: dict[str, AbstractProvider[typing.Any]] = {} source: dict[str, type[Group]] = {} for group in groups: - for attr_name, attr_value in vars(group).items(): - if not isinstance(attr_value, AbstractProvider): - continue + for attr_name, provider in group.get_named_providers().items(): if attr_name in source: prior = source[attr_name] msg = ( @@ -82,7 +80,7 @@ def _collect_fixtures(*groups: type[Group]) -> dict[str, AbstractProvider[typing ) raise ValueError(msg) source[attr_name] = group - providers[attr_name] = attr_value + providers[attr_name] = provider return providers diff --git a/tests/sample.py b/tests/sample.py index f25afd2..a1f534e 100644 --- a/tests/sample.py +++ b/tests/sample.py @@ -29,3 +29,7 @@ class Dependencies(Group): class ExtraDependencies(Group): extra_repo = providers.Factory(scope=Scope.APP, creator=Repo, bound_type=None) + + +class InheritingDependencies(Dependencies): + own_repo = providers.Factory(scope=Scope.APP, creator=Repo, bound_type=None) diff --git a/tests/test_collect_fixtures.py b/tests/test_collect_fixtures.py index f82f51f..4828eab 100644 --- a/tests/test_collect_fixtures.py +++ b/tests/test_collect_fixtures.py @@ -8,7 +8,7 @@ from modern_di import Group, Scope, providers from modern_di_pytest.factory import _collect_fixtures -from tests.sample import Dependencies, ExtraDependencies +from tests.sample import Dependencies, ExtraDependencies, InheritingDependencies def test_collects_providers_by_name() -> None: @@ -19,6 +19,19 @@ def test_collects_providers_by_name() -> None: assert collected["service"] is Dependencies.service +def test_collects_providers_inherited_from_a_parent_group() -> None: + """INVARIANT: a provider inherited from a parent Group becomes a fixture. + + Discovering providers by walking ``vars(group)`` breaks this: ``vars`` sees only + the class's own body, so an inherited provider silently gets no fixture while the + container — which walks the full MRO — still registers and resolves it. + """ + collected = _collect_fixtures(InheritingDependencies) + + assert set(collected) == {"repo", "service", "request_widget", "own_repo"} + assert collected["repo"] is Dependencies.repo + + def test_skips_non_provider_attributes() -> None: collected = _collect_fixtures(Dependencies)