Skip to content

Commit c37edc5

Browse files
test: cover edge-case exception and branch paths
Add tests for the remaining uncovered error/edge-case branches: - Binder.provider_for raising UnknownProvider for an unbindable target - Module configuration re-raising NameError for an unresolvable forward reference in a @Provider return annotation - create_object wrapping a __new__ TypeError in a CallError - _infer_injected_bindings dropping Union members marked NoInject - Injector.get unwrapping a ScopeDecorator to its underlying scope Also mark the import-time logger-level guard with `# pragma: no branch`: its else-branch is only reachable on a fresh import with a pre-set level, which can't be exercised in-process without importlib.reload corrupting shared marker state for the rest of the suite. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 7e5602f commit c37edc5

2 files changed

Lines changed: 71 additions & 4 deletions

File tree

injector/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@
5353
log = logging.getLogger('injector')
5454
log.addHandler(logging.NullHandler())
5555

56-
if log.level == logging.NOTSET:
56+
if log.level == logging.NOTSET: # pragma: no branch
5757
log.setLevel(logging.WARN)
5858

5959
T = TypeVar('T')

injector_100_percent_coverage_test.py

Lines changed: 70 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,21 @@
55
rarely-hit edge-case/error branches purely to drive coverage to 100%.
66
"""
77

8-
from typing import Dict, List
8+
from typing import Dict, List, Union
99

10-
import injector
11-
from injector import UnsatisfiedRequirement
10+
import pytest
1211

12+
import injector
13+
from injector import (
14+
CallError,
15+
Injector,
16+
Module,
17+
NoInject,
18+
UnknownProvider,
19+
UnsatisfiedRequirement,
20+
provider,
21+
singleton,
22+
)
1323

1424
# --- String / representation formatting --------------------------------------
1525

@@ -59,3 +69,60 @@ class FakeDictAlias:
5969

6070
assert injector._get_origin(FakeListAlias) is list
6171
assert injector._get_origin(FakeDictAlias) is dict
72+
73+
74+
# --- Edge-case exception / branch paths --------------------------------------
75+
76+
77+
def test_provider_for_unknown_target_raises():
78+
# Neither a class, callable, provider nor a recognized bindable instance –
79+
# provider_for can't figure out what to do and raises UnknownProvider.
80+
binder = Injector().binder
81+
with pytest.raises(UnknownProvider):
82+
binder.provider_for(123, to=123)
83+
84+
85+
def test_unresolvable_forward_reference_in_provider_raises_name_error():
86+
# The return annotation is a forward reference to a name that never exists,
87+
# so it stays "__deferred__" and re-evaluation at configure time fails.
88+
class BrokenModule(Module):
89+
@provider
90+
def provide(self) -> 'ThisNameNeverExists': # noqa: F821
91+
return object()
92+
93+
with pytest.raises(NameError, match='forward reference'):
94+
Injector([BrokenModule])
95+
96+
97+
def test_create_object_wraps_new_type_error_in_call_error():
98+
# ``cls.__new__(cls)`` raises TypeError (extra required arg), which gets
99+
# re-raised as a CallError.
100+
class NeedsArg:
101+
def __new__(cls, required):
102+
return super().__new__(cls)
103+
104+
with pytest.raises(CallError):
105+
Injector().create_object(NeedsArg)
106+
107+
108+
def test_union_member_marked_noinject_is_dropped():
109+
# A Union whose members carry the NoInject marker is removed from the
110+
# inferred bindings.
111+
def target(x: Union[NoInject[int], str]):
112+
pass
113+
114+
bindings = injector._infer_injected_bindings(target, only_explicit_bindings=False)
115+
assert 'x' not in bindings
116+
117+
118+
def test_get_with_scope_decorator_unwraps_to_scope():
119+
# Passing a ScopeDecorator (e.g. ``singleton``) to Injector.get unwraps it
120+
# to the underlying scope before resolving.
121+
class Service:
122+
pass
123+
124+
inj = Injector()
125+
first = inj.get(Service, scope=singleton)
126+
second = inj.get(Service, scope=singleton)
127+
assert isinstance(first, Service)
128+
assert first is second

0 commit comments

Comments
 (0)