Skip to content

Commit d675b65

Browse files
authored
Merge branch 'master' into rm-2-3.4
2 parents 7be1d74 + 67357f1 commit d675b65

File tree

4 files changed

+109
-81
lines changed

4 files changed

+109
-81
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
@@ -98,17 +98,21 @@ def spy(self, obj, name):
9898

9999
@functools.wraps(method)
100100
def wrapper(*args, **kwargs):
101+
spy_obj.spy_return = None
102+
spy_obj.spy_exception = None
101103
try:
102104
r = method(*args, **kwargs)
103105
except Exception as e:
104-
result.side_effect = e
106+
spy_obj.spy_exception = e
105107
raise
106108
else:
107-
result.return_value = r
109+
spy_obj.spy_return = r
108110
return r
109111

110-
result = self.patch.object(obj, name, side_effect=wrapper, autospec=autospec)
111-
return result
112+
spy_obj = self.patch.object(obj, name, side_effect=wrapper, autospec=autospec)
113+
spy_obj.spy_return = None
114+
spy_obj.spy_exception = None
115+
return spy_obj
112116

113117
def stub(self, name=None):
114118
"""
@@ -189,19 +193,6 @@ def mocker(pytestconfig):
189193
result.stopall()
190194

191195

192-
@pytest.fixture
193-
def mock(mocker):
194-
"""
195-
Same as "mocker", but kept only for backward compatibility.
196-
"""
197-
import warnings
198-
199-
warnings.warn(
200-
'"mock" fixture has been deprecated, use "mocker" instead', DeprecationWarning
201-
)
202-
return mocker
203-
204-
205196
_mock_module_patches = []
206197
_mock_module_originals = {}
207198

tests/test_pytest_mock.py

+47-44
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,27 +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:
250226
def bar(self, arg):
251-
raise Exception(excepted_message)
227+
raise Exception("Error with {}".format(arg))
252228

253229
foo = Foo()
254230
spy = mocker.spy(foo, "bar")
255231

256-
with pytest.raises(Exception) as exc_info:
257-
foo.bar(10)
258-
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)
259236

260-
foo.bar.assert_called_once_with(arg=10)
261-
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
262265

263266

264267
@skip_pypy
@@ -292,7 +295,7 @@ class Foo(Base):
292295
assert other.bar(arg=10) == 20
293296
calls = [mocker.call(foo, arg=10), mocker.call(other, arg=10)]
294297
assert spy.call_args_list == calls
295-
assert spy.return_value == 20
298+
assert spy.spy_return == 20
296299

297300

298301
@skip_pypy
@@ -305,9 +308,9 @@ def bar(cls, arg):
305308
spy = mocker.spy(Foo, "bar")
306309
assert Foo.bar(arg=10) == 20
307310
Foo.bar.assert_called_once_with(arg=10)
308-
assert Foo.bar.return_value == 20
311+
assert Foo.bar.spy_return == 20
309312
spy.assert_called_once_with(arg=10)
310-
assert spy.return_value == 20
313+
assert spy.spy_return == 20
311314

312315

313316
@skip_pypy
@@ -323,9 +326,9 @@ class Foo(Base):
323326
spy = mocker.spy(Foo, "bar")
324327
assert Foo.bar(arg=10) == 20
325328
Foo.bar.assert_called_once_with(arg=10)
326-
assert Foo.bar.return_value == 20
329+
assert Foo.bar.spy_return == 20
327330
spy.assert_called_once_with(arg=10)
328-
assert spy.return_value == 20
331+
assert spy.spy_return == 20
329332

330333

331334
@skip_pypy
@@ -344,9 +347,9 @@ def bar(cls, arg):
344347
spy = mocker.spy(Foo, "bar")
345348
assert Foo.bar(arg=10) == 20
346349
Foo.bar.assert_called_once_with(arg=10)
347-
assert Foo.bar.return_value == 20
350+
assert Foo.bar.spy_return == 20
348351
spy.assert_called_once_with(arg=10)
349-
assert spy.return_value == 20
352+
assert spy.spy_return == 20
350353

351354

352355
@skip_pypy
@@ -359,9 +362,9 @@ def bar(arg):
359362
spy = mocker.spy(Foo, "bar")
360363
assert Foo.bar(arg=10) == 20
361364
Foo.bar.assert_called_once_with(arg=10)
362-
assert Foo.bar.return_value == 20
365+
assert Foo.bar.spy_return == 20
363366
spy.assert_called_once_with(arg=10)
364-
assert spy.return_value == 20
367+
assert spy.spy_return == 20
365368

366369

367370
@skip_pypy
@@ -377,9 +380,9 @@ class Foo(Base):
377380
spy = mocker.spy(Foo, "bar")
378381
assert Foo.bar(arg=10) == 20
379382
Foo.bar.assert_called_once_with(arg=10)
380-
assert Foo.bar.return_value == 20
383+
assert Foo.bar.spy_return == 20
381384
spy.assert_called_once_with(arg=10)
382-
assert spy.return_value == 20
385+
assert spy.spy_return == 20
383386

384387

385388
def test_callable_like_spy(testdir, mocker):
@@ -399,7 +402,7 @@ def __call__(self, x):
399402
spy = mocker.spy(uut, "call_like")
400403
uut.call_like(10)
401404
spy.assert_called_once_with(10)
402-
assert spy.return_value == 20
405+
assert spy.spy_return == 20
403406

404407

405408
@contextmanager

0 commit comments

Comments
 (0)