Skip to content

Commit 0e13a0c

Browse files
authored
docs(matchers): add section on custom matchers (#210)
1 parent 9ea6fad commit 0e13a0c

File tree

1 file changed

+45
-0
lines changed

1 file changed

+45
-0
lines changed

docs/usage/matchers.md

+45
Original file line numberDiff line numberDiff line change
@@ -80,3 +80,48 @@ def test_event_listener(decoy: Decoy):
8080
This is a pretty verbose way of writing a test, so in general, you may want to approach using `matchers.Captor` as a form of potential code smell / test pain. There are often better ways to structure your code for these sorts of interactions that don't involve private functions.
8181

8282
For further reading on when (or rather, when not) to use argument captors, check out [testdouble's documentation on its argument captor matcher](https://github.com/testdouble/testdouble.js/blob/main/docs/6-verifying-invocations.md#tdmatcherscaptor).
83+
84+
## Writing custom matchers
85+
86+
You can write your own matcher class and use it wherever you would use a built-in matcher. All you need to do is define a class with an `__eq__` method:
87+
88+
```python
89+
class Is42:
90+
def __eq__(self, other: object) -> bool:
91+
return other == 42
92+
93+
check_answer = decoy.mock(name="check_answer")
94+
95+
decoy.when(
96+
check_answer(Is42())
97+
).then_return("huzzah!")
98+
99+
assert check_answer(42) == "huzzah!"
100+
assert check_answer(43) is None
101+
```
102+
103+
This is especially useful if the value objects you are using as arguments are difficult to compare and out of your control. For example, Pandas [DataFrame][] objects do not return a `bool` from `__eq__`, which makes it difficult to compare calls.
104+
105+
We can define a `MatchesDataFrame` class to work around this:
106+
107+
```python
108+
import pandas as pd
109+
110+
class MatchesDataFrame:
111+
def __init__(self, data) -> None:
112+
self._data_frame = pd.DataFrame(data)
113+
114+
def __eq__(self, other: object) -> bool:
115+
return self._data_frame.equals(other)
116+
117+
check_data = decoy.mock(name="check_data")
118+
119+
decoy.when(
120+
check_answer(MatchesDataFrame({"x1": range(1, 42)}))
121+
).then_return("huzzah!")
122+
123+
assert check_data(pd.DataFrame({"x1": range(1, 42)})) == "huzzah!"
124+
assert check_data(pd.DataFrame({"x1": range(1, 43)})) is None
125+
```
126+
127+
[DataFrame]: https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.html

0 commit comments

Comments
 (0)