Skip to content

Commit e7af3f0

Browse files
authored
refactor(matchers): simplify branching logic and add test coverage (#303)
1 parent 151a4ae commit e7af3f0

File tree

2 files changed

+77
-19
lines changed

2 files changed

+77
-19
lines changed

decoy/matchers.py

Lines changed: 12 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -178,14 +178,11 @@ def __init__(self, attributes: Mapping[str, Any]) -> None:
178178

179179
def __eq__(self, target: object) -> bool:
180180
"""Return true if target matches all given attributes."""
181-
is_match = True
182181
for attr_name, value in self._attributes.items():
183-
if is_match:
184-
is_match = (
185-
hasattr(target, attr_name) and getattr(target, attr_name) == value
186-
)
182+
if not hasattr(target, attr_name) or getattr(target, attr_name) != value:
183+
return False
187184

188-
return is_match
185+
return True
189186

190187
def __repr__(self) -> str:
191188
"""Return a string representation of the matcher."""
@@ -219,16 +216,14 @@ def __init__(self, values: Mapping[Any, Any]) -> None:
219216

220217
def __eq__(self, target: object) -> bool:
221218
"""Return true if target matches all given keys/values."""
222-
is_match = True
223-
224219
for key, value in self._values.items():
225-
if is_match:
226-
try:
227-
is_match = key in target and target[key] == value # type: ignore[index,operator]
228-
except TypeError:
229-
is_match = False
220+
try:
221+
if key not in target or target[key] != value: # type: ignore[index,operator]
222+
return False
223+
except TypeError:
224+
return False
230225

231-
return is_match
226+
return True
232227

233228
def __repr__(self) -> str:
234229
"""Return a string representation of the matcher."""
@@ -319,10 +314,12 @@ def StringMatching(match: str) -> str:
319314
class _ErrorMatching:
320315
_error_type: Type[BaseException]
321316
_string_matcher: Optional[_StringMatching]
317+
_match: Optional[str]
322318

323319
def __init__(self, error: Type[BaseException], match: Optional[str] = None) -> None:
324320
"""Initialize with the Exception type and optional message matcher."""
325321
self._error_type = error
322+
self._match = match
326323
self._string_matcher = _StringMatching(match) if match is not None else None
327324

328325
def __eq__(self, target: object) -> bool:
@@ -338,9 +335,7 @@ def __eq__(self, target: object) -> bool:
338335

339336
def __repr__(self) -> str:
340337
"""Return a string representation of the matcher."""
341-
return (
342-
f"<ErrorMatching {self._error_type.__name__} match={self._string_matcher}>"
343-
)
338+
return f"<ErrorMatching {self._error_type.__name__} match={self._match!r}>"
344339

345340

346341
ErrorT = TypeVar("ErrorT", bound=BaseException)

tests/test_matchers.py

Lines changed: 65 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,9 @@ def test_anything_or_none_matcher() -> None:
3131
assert SomeClass() == matchers.AnythingOrNone()
3232
assert None == matchers.AnythingOrNone() # noqa: E711
3333

34+
35+
def test_anything_or_none_repr() -> None:
36+
"""`AnythingOrNone()` has a string representation."""
3437
assert str(matchers.AnythingOrNone()) == "<AnythingOrNone>"
3538

3639

@@ -45,6 +48,11 @@ def test_any_matcher() -> None:
4548
assert None != matchers.Anything() # noqa: E711
4649

4750

51+
def test_anything_repr() -> None:
52+
"""`Anything()` has a string representation."""
53+
assert str(matchers.Anything()) == "<Anything>"
54+
55+
4856
def test_is_a_matcher() -> None:
4957
"""It should have an "anything that is this type" matcher."""
5058
assert 1 == matchers.IsA(int)
@@ -65,6 +73,19 @@ def test_is_a_matcher_checks_instance(decoy: Decoy) -> None:
6573
assert target == matchers.IsA(SomeClass)
6674

6775

76+
def test_is_a_repr() -> None:
77+
"""`IsA()` has a string representation."""
78+
assert str(matchers.IsA(SomeClass)) == "<IsA SomeClass>"
79+
80+
81+
def test_is_a_with_attrs_repr() -> None:
82+
"""`IsA()` has a string representation when passed attributes."""
83+
assert (
84+
str(matchers.IsA(SomeClass, {"hello": "world"}))
85+
== "<IsA SomeClass {'hello': 'world'}>"
86+
)
87+
88+
6889
def test_is_not_matcher() -> None:
6990
"""It should have an "anything that isn't this" matcher."""
7091
assert 1 == matchers.IsNot(2)
@@ -80,19 +101,35 @@ def test_is_not_matcher() -> None:
80101
assert ("hello", "world") != matchers.IsNot(("hello", "world"))
81102

82103

83-
def test_has_attribute_matcher() -> None:
104+
def test_is_not_repr() -> None:
105+
"""`IsNot()` has a string representation."""
106+
assert str(matchers.IsNot("hello")) == "<IsNot 'hello'>"
107+
108+
109+
def test_has_attributes_matcher() -> None:
84110
"""It should have an "anything with these attributes" matcher."""
85111
assert _HelloTuple("world") == matchers.HasAttributes({"hello": "world"})
86112
assert _HelloClass() == matchers.HasAttributes({"hello": "world"})
87113
assert _HelloClass() == matchers.HasAttributes({"goodbye": "so long"})
88114

89115
assert {"hello": "world"} != matchers.HasAttributes({"hello": "world"})
90-
assert _HelloTuple("world") != matchers.HasAttributes({"goodbye": "so long"})
116+
assert _HelloTuple("world") != matchers.HasAttributes({"hello": "goodbye"})
117+
assert _HelloTuple("world") != matchers.HasAttributes(
118+
{"hello": "world", "goodbye": "so long"}
119+
)
91120
assert 1 != matchers.HasAttributes({"hello": "world"})
92121
assert False != matchers.HasAttributes({"hello": "world"}) # noqa: E712
93122
assert [] != matchers.HasAttributes({"hello": "world"})
94123

95124

125+
def test_has_attributes_repr() -> None:
126+
"""`HasAttributes()` has a string representation."""
127+
assert (
128+
str(matchers.HasAttributes({"hello": "world"}))
129+
== "<HasAttributes {'hello': 'world'}>"
130+
)
131+
132+
96133
def test_dict_matching_matcher() -> None:
97134
"""It should have a "dictionary matching" matcher."""
98135
assert {"hello": "world"} == matchers.DictMatching({"hello": "world"})
@@ -114,6 +151,11 @@ def test_dict_matching_matcher_with_int_key() -> None:
114151
assert {1: "hello", 2: "world"} == matchers.DictMatching({2: "world"})
115152

116153

154+
def test_dict_matching_repr() -> None:
155+
"""`DictMatching()` has a string representation."""
156+
assert str(matchers.DictMatching({1: "hello"})) == "<DictMatching {1: 'hello'}>"
157+
158+
117159
def test_list_matching_matcher() -> None:
118160
"""It should have a "contains this sub-list" matcher."""
119161
assert [1, 2, 3] == matchers.ListMatching([1])
@@ -134,6 +176,9 @@ def test_list_matching_matcher() -> None:
134176

135177
assert 1 != matchers.ListMatching([1])
136178

179+
180+
def test_list_matching_repr() -> None:
181+
"""`ListMatching()` has a string representation."""
137182
assert str(matchers.ListMatching([1])) == "<ListMatching [1]>"
138183

139184

@@ -143,6 +188,11 @@ def test_string_matching_matcher() -> None:
143188
assert "hello" != matchers.StringMatching("^ello$")
144189

145190

191+
def test_string_matching_repr() -> None:
192+
"""`StringMatching()` has a string representation."""
193+
assert str(matchers.StringMatching("ello")) == "<StringMatching 'ello'>"
194+
195+
146196
def test_error_matching_matcher() -> None:
147197
"""It should have an "any error that matches" matcher."""
148198
assert RuntimeError("ah!") == matchers.ErrorMatching(RuntimeError)
@@ -151,6 +201,14 @@ def test_error_matching_matcher() -> None:
151201
assert RuntimeError("ah!") != matchers.ErrorMatching(RuntimeError, "ah$")
152202

153203

204+
def test_error_matching_repr() -> None:
205+
"""`ErrorMatching()` has a string representation."""
206+
assert (
207+
str(matchers.ErrorMatching(RuntimeError, "ah"))
208+
== "<ErrorMatching RuntimeError match='ah'>"
209+
)
210+
211+
154212
def test_captor_matcher_legacy() -> None:
155213
"""It should have a captor matcher that captures the compared value."""
156214
captor = matchers.Captor()
@@ -195,3 +253,8 @@ def test_captor_matcher_raises_if_no_value() -> None:
195253

196254
with pytest.raises(AssertionError, match="No value captured"):
197255
captor.value # noqa: B018
256+
257+
258+
def test_captor_repr() -> None:
259+
"""`StringMatching()` has a string representation."""
260+
assert str(matchers.ValueCaptor()) == "<Captor>"

0 commit comments

Comments
 (0)