Skip to content

Commit 67357f1

Browse files
authored
Change spy attributes to 'spy_return' and 'spy_exception' (#177)
Change spy attributes to 'spy_return' and 'spy_exception'
2 parents 7bddcd5 + ccb76e5 commit 67357f1

File tree

4 files changed

+109
-82
lines changed

4 files changed

+109
-82
lines changed

CHANGELOG.rst

+20
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,23 @@
1+
2.0.0 (2020-01-04)
2+
------------------
3+
4+
Breaking Changes
5+
++++++++++++++++
6+
7+
* ``mocker.spy`` attributes for tracking returned values and raised exceptions of its spied functions
8+
are now called ``spy_return`` and ``spy_exception``, instead of reusing the existing
9+
``MagicMock`` attributes ``return_value`` and ``side_effect``.
10+
11+
Version ``1.13`` introduced a serious regression: after a spied function using ``mocker.spy``
12+
raises an exception, further calls to the spy will not call the spied function,
13+
always raising the first exception instead: assigning to ``side_effect`` causes
14+
``unittest.mock`` to behave this way (`#175`_).
15+
16+
* The deprecated ``mock`` alias to the ``mocker`` fixture has finally been removed.
17+
18+
.. _#175: https://github.com/pytest-dev/pytest-mock/issues/175
19+
20+
121
1.13.0 (2019-12-05)
222
-------------------
323

README.rst

+34-20
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,8 @@
22
pytest-mock
33
===========
44

5-
This plugin installs a ``mocker`` fixture which is a thin-wrapper around the patching API
6-
provided by the `mock package <http://pypi.python.org/pypi/mock>`_,
7-
but with the benefit of not having to worry about undoing patches at the end
8-
of a test:
5+
This plugin provides a ``mocker`` fixture which is a thin-wrapper around the patching API
6+
provided by the `mock package <http://pypi.python.org/pypi/mock>`_:
97

108
.. code-block:: python
119
@@ -23,6 +21,9 @@ of a test:
2321
os.remove.assert_called_once_with('file')
2422
2523
24+
Besides undoing the mocking automatically after the end of the test, it also provides other
25+
nice utilities such as ``spy`` and ``stub``, and uses pytest introspection when
26+
comparing calls.
2627

2728
|python| |version| |anaconda| |ci| |coverage| |black|
2829

@@ -70,7 +71,7 @@ The supported methods are:
7071
* `mocker.stopall <https://docs.python.org/3/library/unittest.mock.html#unittest.mock.patch.stopall>`_
7172
* ``mocker.resetall()``: calls `reset_mock() <https://docs.python.org/3/library/unittest.mock.html#unittest.mock.Mock.reset_mock>`_ in all mocked objects up to this point.
7273

73-
These objects from the ``mock`` module are accessible directly from ``mocker`` for convenience:
74+
Also, as a convenience, these names from the ``mock`` module are accessible directly from ``mocker``:
7475

7576
* `Mock <https://docs.python.org/3/library/unittest.mock.html#unittest.mock.Mock>`_
7677
* `MagicMock <https://docs.python.org/3/library/unittest.mock.html#unittest.mock.MagicMock>`_
@@ -85,33 +86,47 @@ These objects from the ``mock`` module are accessible directly from ``mocker`` f
8586
Spy
8687
---
8788

88-
The spy acts exactly like the original method in all cases, except it allows use of ``mock``
89-
features with it, like retrieving call count. It also works for class and static methods.
89+
The ``mocker.spy`` object acts exactly like the original method in all cases, except the spy
90+
also tracks method calls, return values and exceptions raised.
9091

9192
.. code-block:: python
9293
9394
def test_spy(mocker):
9495
class Foo(object):
95-
def bar(self):
96-
return 42
96+
def bar(self, v):
97+
return v * 2
9798
9899
foo = Foo()
99-
mocker.spy(foo, 'bar')
100-
assert foo.bar() == 42
101-
assert foo.bar.call_count == 1
100+
spy = mocker.spy(foo, 'bar')
101+
assert foo.bar(21) == 42
102102
103-
Since version ``1.11``, it is also possible to query the ``return_value`` attribute
104-
to observe what the spied function/method returned.
103+
spy.assert_called_once_with(21)
104+
assert spy.spy_return == 42
105105
106-
Since version ``1.13``, it is also possible to query the ``side_effect`` attribute
107-
to observe any exception thrown by the spied function/method.
106+
The object returned by ``mocker.spy`` is a ``MagicMock`` object, so all standard checking functions
107+
are available (like ``assert_called_once_with`` in the example above).
108+
109+
In addition, spy objects contain two extra attributes:
110+
111+
* ``spy_return``: contains the returned value of the spied function.
112+
* ``spy_exception``: contain the last exception value raised by the spied function/method when
113+
it was last called, or ``None`` if no exception was raised.
114+
115+
``mocker.spy`` also works for class and static methods.
116+
117+
.. note::
118+
119+
In versions earlier than ``2.0``, the attributes were called ``return_value`` and
120+
``side_effect`` respectively, but due to incompatibilities with ``unittest.mock``
121+
they had to be renamed (see `#175`_ for details).
122+
123+
.. _#175: https://github.com/pytest-dev/pytest-mock/issues/175
108124

109125
Stub
110126
----
111127

112-
113-
The stub is a mock object that accepts any arguments and is useful to test callbacks, for instance.
114-
May be passed a name to be used by the constructed stub object in its repr (useful for debugging).
128+
The stub is a mock object that accepts any arguments and is useful to test callbacks.
129+
It may receive an optional name that is shown in its ``repr``, useful for debugging.
115130

116131
.. code-block:: python
117132
@@ -128,7 +143,6 @@ May be passed a name to be used by the constructed stub object in its repr (usef
128143
Improved reporting of mock call assertion errors
129144
------------------------------------------------
130145

131-
132146
This plugin monkeypatches the mock library to improve pytest output for failures
133147
of mock call assertions like ``Mock.assert_called_with()`` by hiding internal traceback
134148
entries from the ``mock`` module.

src/pytest_mock/plugin.py

+8-17
Original file line numberDiff line numberDiff line change
@@ -113,17 +113,21 @@ def spy(self, obj, name):
113113

114114
@w
115115
def wrapper(*args, **kwargs):
116+
spy_obj.spy_return = None
117+
spy_obj.spy_exception = None
116118
try:
117119
r = method(*args, **kwargs)
118120
except Exception as e:
119-
result.side_effect = e
121+
spy_obj.spy_exception = e
120122
raise
121123
else:
122-
result.return_value = r
124+
spy_obj.spy_return = r
123125
return r
124126

125-
result = self.patch.object(obj, name, side_effect=wrapper, autospec=autospec)
126-
return result
127+
spy_obj = self.patch.object(obj, name, side_effect=wrapper, autospec=autospec)
128+
spy_obj.spy_return = None
129+
spy_obj.spy_exception = None
130+
return spy_obj
127131

128132
def stub(self, name=None):
129133
"""
@@ -204,19 +208,6 @@ def mocker(pytestconfig):
204208
result.stopall()
205209

206210

207-
@pytest.fixture
208-
def mock(mocker):
209-
"""
210-
Same as "mocker", but kept only for backward compatibility.
211-
"""
212-
import warnings
213-
214-
warnings.warn(
215-
'"mock" fixture has been deprecated, use "mocker" instead', DeprecationWarning
216-
)
217-
return mocker
218-
219-
220211
_mock_module_patches = []
221212
_mock_module_originals = {}
222213

tests/test_pytest_mock.py

+47-45
Original file line numberDiff line numberDiff line change
@@ -132,28 +132,6 @@ def test_mock_patch_dict_resetall(mocker):
132132
assert x == {"new": 10}
133133

134134

135-
def test_deprecated_mock(testdir):
136-
"""
137-
Use backward-compatibility-only mock fixture to ensure complete coverage.
138-
"""
139-
p1 = testdir.makepyfile(
140-
"""
141-
import os
142-
143-
def test(mock, tmpdir):
144-
mock.patch("os.listdir", return_value=["mocked"])
145-
assert os.listdir(str(tmpdir)) == ["mocked"]
146-
mock.stopall()
147-
assert os.listdir(str(tmpdir)) == []
148-
"""
149-
)
150-
result = testdir.runpytest(str(p1))
151-
result.stdout.fnmatch_lines(
152-
['*DeprecationWarning: "mock" fixture has been deprecated, use "mocker"*']
153-
)
154-
assert result.ret == 0
155-
156-
157135
@pytest.mark.parametrize(
158136
"name",
159137
[
@@ -238,28 +216,52 @@ def bar(self, arg):
238216
assert foo.bar(arg=10) == 20
239217
assert other.bar(arg=10) == 20
240218
foo.bar.assert_called_once_with(arg=10)
241-
assert foo.bar.return_value == 20
219+
assert foo.bar.spy_return == 20
242220
spy.assert_called_once_with(arg=10)
243-
assert spy.return_value == 20
221+
assert spy.spy_return == 20
244222

245223

246224
def test_instance_method_spy_exception(mocker):
247-
excepted_message = "foo"
248-
249225
class Foo(object):
250226
def bar(self, arg):
251-
raise Exception(excepted_message)
227+
raise Exception("Error with {}".format(arg))
252228

253229
foo = Foo()
254-
other = Foo()
255230
spy = mocker.spy(foo, "bar")
256231

257-
with pytest.raises(Exception) as exc_info:
258-
foo.bar(10)
259-
assert str(exc_info.value) == excepted_message
232+
expected_calls = []
233+
for i, v in enumerate([10, 20]):
234+
with pytest.raises(Exception, match="Error with {}".format(v)) as exc_info:
235+
foo.bar(arg=v)
260236

261-
foo.bar.assert_called_once_with(arg=10)
262-
assert spy.side_effect == exc_info.value
237+
expected_calls.append(mocker.call(arg=v))
238+
assert foo.bar.call_args_list == expected_calls
239+
assert str(spy.spy_exception) == "Error with {}".format(v)
240+
241+
242+
def test_spy_reset(mocker):
243+
class Foo(object):
244+
def bar(self, x):
245+
if x == 0:
246+
raise ValueError("invalid x")
247+
return x * 3
248+
249+
spy = mocker.spy(Foo, "bar")
250+
assert spy.spy_return is None
251+
assert spy.spy_exception is None
252+
253+
Foo().bar(10)
254+
assert spy.spy_return == 30
255+
assert spy.spy_exception is None
256+
257+
with pytest.raises(ValueError):
258+
Foo().bar(0)
259+
assert spy.spy_return is None
260+
assert str(spy.spy_exception) == "invalid x"
261+
262+
Foo().bar(15)
263+
assert spy.spy_return == 45
264+
assert spy.spy_exception is None
263265

264266

265267
@skip_pypy
@@ -293,7 +295,7 @@ class Foo(Base):
293295
assert other.bar(arg=10) == 20
294296
calls = [mocker.call(foo, arg=10), mocker.call(other, arg=10)]
295297
assert spy.call_args_list == calls
296-
assert spy.return_value == 20
298+
assert spy.spy_return == 20
297299

298300

299301
@skip_pypy
@@ -306,9 +308,9 @@ def bar(cls, arg):
306308
spy = mocker.spy(Foo, "bar")
307309
assert Foo.bar(arg=10) == 20
308310
Foo.bar.assert_called_once_with(arg=10)
309-
assert Foo.bar.return_value == 20
311+
assert Foo.bar.spy_return == 20
310312
spy.assert_called_once_with(arg=10)
311-
assert spy.return_value == 20
313+
assert spy.spy_return == 20
312314

313315

314316
@skip_pypy
@@ -325,9 +327,9 @@ class Foo(Base):
325327
spy = mocker.spy(Foo, "bar")
326328
assert Foo.bar(arg=10) == 20
327329
Foo.bar.assert_called_once_with(arg=10)
328-
assert Foo.bar.return_value == 20
330+
assert Foo.bar.spy_return == 20
329331
spy.assert_called_once_with(arg=10)
330-
assert spy.return_value == 20
332+
assert spy.spy_return == 20
331333

332334

333335
@skip_pypy
@@ -346,9 +348,9 @@ def bar(cls, arg):
346348
spy = mocker.spy(Foo, "bar")
347349
assert Foo.bar(arg=10) == 20
348350
Foo.bar.assert_called_once_with(arg=10)
349-
assert Foo.bar.return_value == 20
351+
assert Foo.bar.spy_return == 20
350352
spy.assert_called_once_with(arg=10)
351-
assert spy.return_value == 20
353+
assert spy.spy_return == 20
352354

353355

354356
@skip_pypy
@@ -361,9 +363,9 @@ def bar(arg):
361363
spy = mocker.spy(Foo, "bar")
362364
assert Foo.bar(arg=10) == 20
363365
Foo.bar.assert_called_once_with(arg=10)
364-
assert Foo.bar.return_value == 20
366+
assert Foo.bar.spy_return == 20
365367
spy.assert_called_once_with(arg=10)
366-
assert spy.return_value == 20
368+
assert spy.spy_return == 20
367369

368370

369371
@skip_pypy
@@ -380,9 +382,9 @@ class Foo(Base):
380382
spy = mocker.spy(Foo, "bar")
381383
assert Foo.bar(arg=10) == 20
382384
Foo.bar.assert_called_once_with(arg=10)
383-
assert Foo.bar.return_value == 20
385+
assert Foo.bar.spy_return == 20
384386
spy.assert_called_once_with(arg=10)
385-
assert spy.return_value == 20
387+
assert spy.spy_return == 20
386388

387389

388390
def test_callable_like_spy(testdir, mocker):
@@ -402,7 +404,7 @@ def __call__(self, x):
402404
spy = mocker.spy(uut, "call_like")
403405
uut.call_like(10)
404406
spy.assert_called_once_with(10)
405-
assert spy.return_value == 20
407+
assert spy.spy_return == 20
406408

407409

408410
@contextmanager

0 commit comments

Comments
 (0)