Skip to content

Commit 864aad7

Browse files
authored
fix(when): ensure then_enter_with accepts None (#137)
Fixes #135
1 parent c23d145 commit 864aad7

File tree

3 files changed

+42
-6
lines changed

3 files changed

+42
-6
lines changed

decoy/call_handler.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
from .spy_log import SpyLog
55
from .context_managers import ContextWrapper
66
from .spy_events import SpyCall, SpyEvent
7-
from .stub_store import StubStore
7+
from .stub_store import MISSING, StubStore
88

99

1010
class CallHandlerResult(NamedTuple):
@@ -43,7 +43,7 @@ def handle(self, call: SpyEvent) -> Optional[CallHandlerResult]:
4343
else:
4444
return_value = behavior.action()
4545

46-
elif behavior.context_value:
46+
elif behavior.context_value is not MISSING:
4747
return_value = ContextWrapper(behavior.context_value)
4848

4949
else:

decoy/stub_store.py

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,26 @@
11
"""Stub creation and storage."""
2-
from typing import Any, Callable, List, NamedTuple, Optional
2+
from typing import Any, Callable, List, NamedTuple, Optional, Union
33

44
from .spy_events import SpyEvent, WhenRehearsal, match_event
55

66

7+
class _MISSING:
8+
pass
9+
10+
11+
MISSING = _MISSING()
12+
"""Value not specified sentinel.
13+
14+
Used when `None` could be a valid value,
15+
so `Optional` would be inappropriate.
16+
"""
17+
18+
719
class StubBehavior(NamedTuple):
820
"""A recorded stub behavior."""
921

1022
return_value: Optional[Any] = None
11-
context_value: Optional[Any] = None
23+
context_value: Union[_MISSING, Any] = MISSING
1224
error: Optional[Exception] = None
1325
action: Optional[Callable[..., Any]] = None
1426
once: bool = False

tests/test_call_handler.py

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -145,9 +145,11 @@ def test_handle_call_with_context_enter(
145145
stub_store: StubStore,
146146
subject: CallHandler,
147147
) -> None:
148-
"""It return a Stub's configured context value."""
148+
"""It should return a Stub's configured context value."""
149149
spy_call = SpyEvent(
150-
spy_id=42, spy_name="spy_name", payload=SpyCall(args=(), kwargs={})
150+
spy_id=42,
151+
spy_name="spy_name",
152+
payload=SpyCall(args=(), kwargs={}),
151153
)
152154
behavior = StubBehavior(context_value="hello world")
153155

@@ -157,3 +159,25 @@ def test_handle_call_with_context_enter(
157159
assert result == "hello world"
158160

159161
decoy.verify(spy_log.push(spy_call))
162+
163+
164+
def test_handle_call_with_context_enter_none(
165+
decoy: Decoy,
166+
spy_log: SpyLog,
167+
stub_store: StubStore,
168+
subject: CallHandler,
169+
) -> None:
170+
"""It should allow a configured context value to be None."""
171+
spy_call = SpyEvent(
172+
spy_id=42,
173+
spy_name="spy_name",
174+
payload=SpyCall(args=(), kwargs={}),
175+
)
176+
behavior = StubBehavior(context_value=None)
177+
178+
decoy.when(stub_store.get_by_call(spy_call)).then_return(behavior)
179+
180+
with subject.handle(spy_call).value as result: # type: ignore[union-attr]
181+
assert result is None
182+
183+
decoy.verify(spy_log.push(spy_call))

0 commit comments

Comments
 (0)