Skip to content

Commit 9e74b36

Browse files
authored
fix(verify): improve verify traceback and error messages (#66)
- Ensure verify error message is helpful when actual call count matches the `times` argument - Use `__tracebackhide__` to hide Decoy code from pytest traceback
1 parent 9473401 commit 9e74b36

File tree

6 files changed

+41
-12
lines changed

6 files changed

+41
-12
lines changed

decoy/__init__.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@
55
from .core import DecoyCore, StubCore
66
from .types import ClassT, FuncT, ReturnT
77

8+
# ensure decoy does not pollute pytest tracebacks
9+
__tracebackhide__ = True
10+
811

912
class Decoy:
1013
"""Decoy test double state container."""

decoy/core.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@
1010
from .warning_checker import WarningChecker
1111
from .types import ReturnT
1212

13+
# ensure decoy.core does not pollute Pytest tracebacks
14+
__tracebackhide__ = True
15+
1316

1417
class DecoyCore:
1518
"""The DecoyCore class implements the main logic of Decoy."""

decoy/errors.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ def __init__(
6060
heading=heading,
6161
rehearsals=rehearsals,
6262
calls=calls,
63-
include_calls=times is None,
63+
include_calls=times is None or times == len(calls),
6464
)
6565

6666
super().__init__(message)

decoy/stringify.py

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -31,21 +31,24 @@ def count(count: int, noun: str) -> str:
3131
return f"{count} {noun}{'s' if count != 1 else ''}"
3232

3333

34+
def join_lines(*lines: str) -> str:
35+
"""Join a list of lines with newline characters."""
36+
return os.linesep.join(lines).strip()
37+
38+
3439
def stringify_error_message(
3540
heading: str,
3641
rehearsals: Sequence[BaseSpyRehearsal],
3742
calls: Sequence[SpyCall],
3843
include_calls: bool = True,
3944
) -> str:
4045
"""Stringify an error message about a rehearsals to calls comparison."""
41-
return os.linesep.join(
42-
[
43-
heading,
44-
stringify_call_list(rehearsals),
45-
(
46-
f"Found {count(len(calls), 'call')}"
47-
f"{'.' if len(calls) == 0 or not include_calls else ':'}"
48-
),
49-
stringify_call_list(calls) if include_calls else "",
50-
]
51-
).strip()
46+
return join_lines(
47+
heading,
48+
stringify_call_list(rehearsals),
49+
(
50+
f"Found {count(len(calls), 'call')}"
51+
f"{'.' if len(calls) == 0 or not include_calls else ':'}"
52+
),
53+
stringify_call_list(calls) if include_calls else "",
54+
)

decoy/verifier.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@
44
from .spy_calls import SpyCall, VerifyRehearsal, match_call
55
from .errors import VerifyError
66

7+
# ensure decoy.verifier does not pollute Pytest tracebacks
8+
__tracebackhide__ = True
9+
710

811
class Verifier:
912
"""An interface to verify that spies were called as expected."""

tests/test_errors.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,23 @@ class VerifyErrorSpec(NamedTuple):
9292
]
9393
),
9494
),
95+
VerifyErrorSpec(
96+
rehearsals=[
97+
VerifyRehearsal(spy_id=101, spy_name="spy_101", args=(1, 2, 3), kwargs={}),
98+
],
99+
calls=[
100+
SpyCall(spy_id=101, spy_name="spy_101", args=(4, 5, 6), kwargs={}),
101+
],
102+
times=1,
103+
expected_message=os.linesep.join(
104+
[
105+
"Expected exactly 1 call:",
106+
"1.\tspy_101(1, 2, 3)",
107+
"Found 1 call:",
108+
"1.\tspy_101(4, 5, 6)",
109+
]
110+
),
111+
),
95112
]
96113

97114

0 commit comments

Comments
 (0)