Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 735fc2c

Browse files
authored
gh-75988: Fix issues with autospec ignoring wrapped object (#115223)
* set default return value of functional types as _mock_return_value * added test of wrapping child attributes * added backward compatibility with explicit return * added docs on the order of precedence * added test to check default return_value
1 parent 7db871e commit 735fc2c

File tree

4 files changed

+199
-2
lines changed

4 files changed

+199
-2
lines changed

Doc/library/unittest.mock.rst

+120
Original file line numberDiff line numberDiff line change
@@ -2831,3 +2831,123 @@ Sealing mocks
28312831
>>> mock.not_submock.attribute2 # This won't raise.
28322832

28332833
.. versionadded:: 3.7
2834+
2835+
2836+
Order of precedence of :attr:`side_effect`, :attr:`return_value` and *wraps*
2837+
----------------------------------------------------------------------------
2838+
2839+
The order of their precedence is:
2840+
2841+
1. :attr:`~Mock.side_effect`
2842+
2. :attr:`~Mock.return_value`
2843+
3. *wraps*
2844+
2845+
If all three are set, mock will return the value from :attr:`~Mock.side_effect`,
2846+
ignoring :attr:`~Mock.return_value` and the wrapped object altogether. If any
2847+
two are set, the one with the higher precedence will return the value.
2848+
Regardless of the order of which was set first, the order of precedence
2849+
remains unchanged.
2850+
2851+
>>> from unittest.mock import Mock
2852+
>>> class Order:
2853+
... @staticmethod
2854+
... def get_value():
2855+
... return "third"
2856+
...
2857+
>>> order_mock = Mock(spec=Order, wraps=Order)
2858+
>>> order_mock.get_value.side_effect = ["first"]
2859+
>>> order_mock.get_value.return_value = "second"
2860+
>>> order_mock.get_value()
2861+
'first'
2862+
2863+
As ``None`` is the default value of :attr:`~Mock.side_effect`, if you reassign
2864+
its value back to ``None``, the order of precedence will be checked between
2865+
:attr:`~Mock.return_value` and the wrapped object, ignoring
2866+
:attr:`~Mock.side_effect`.
2867+
2868+
>>> order_mock.get_value.side_effect = None
2869+
>>> order_mock.get_value()
2870+
'second'
2871+
2872+
If the value being returned by :attr:`~Mock.side_effect` is :data:`DEFAULT`,
2873+
it is ignored and the order of precedence moves to the successor to obtain the
2874+
value to return.
2875+
2876+
>>> from unittest.mock import DEFAULT
2877+
>>> order_mock.get_value.side_effect = [DEFAULT]
2878+
>>> order_mock.get_value()
2879+
'second'
2880+
2881+
When :class:`Mock` wraps an object, the default value of
2882+
:attr:`~Mock.return_value` will be :data:`DEFAULT`.
2883+
2884+
>>> order_mock = Mock(spec=Order, wraps=Order)
2885+
>>> order_mock.return_value
2886+
sentinel.DEFAULT
2887+
>>> order_mock.get_value.return_value
2888+
sentinel.DEFAULT
2889+
2890+
The order of precedence will ignore this value and it will move to the last
2891+
successor which is the wrapped object.
2892+
2893+
As the real call is being made to the wrapped object, creating an instance of
2894+
this mock will return the real instance of the class. The positional arguments,
2895+
if any, required by the wrapped object must be passed.
2896+
2897+
>>> order_mock_instance = order_mock()
2898+
>>> isinstance(order_mock_instance, Order)
2899+
True
2900+
>>> order_mock_instance.get_value()
2901+
'third'
2902+
2903+
>>> order_mock.get_value.return_value = DEFAULT
2904+
>>> order_mock.get_value()
2905+
'third'
2906+
2907+
>>> order_mock.get_value.return_value = "second"
2908+
>>> order_mock.get_value()
2909+
'second'
2910+
2911+
But if you assign ``None`` to it, this will not be ignored as it is an
2912+
explicit assignment. So, the order of precedence will not move to the wrapped
2913+
object.
2914+
2915+
>>> order_mock.get_value.return_value = None
2916+
>>> order_mock.get_value() is None
2917+
True
2918+
2919+
Even if you set all three at once when initializing the mock, the order of
2920+
precedence remains the same:
2921+
2922+
>>> order_mock = Mock(spec=Order, wraps=Order,
2923+
... **{"get_value.side_effect": ["first"],
2924+
... "get_value.return_value": "second"}
2925+
... )
2926+
...
2927+
>>> order_mock.get_value()
2928+
'first'
2929+
>>> order_mock.get_value.side_effect = None
2930+
>>> order_mock.get_value()
2931+
'second'
2932+
>>> order_mock.get_value.return_value = DEFAULT
2933+
>>> order_mock.get_value()
2934+
'third'
2935+
2936+
If :attr:`~Mock.side_effect` is exhausted, the order of precedence will not
2937+
cause a value to be obtained from the successors. Instead, ``StopIteration``
2938+
exception is raised.
2939+
2940+
>>> order_mock = Mock(spec=Order, wraps=Order)
2941+
>>> order_mock.get_value.side_effect = ["first side effect value",
2942+
... "another side effect value"]
2943+
>>> order_mock.get_value.return_value = "second"
2944+
2945+
>>> order_mock.get_value()
2946+
'first side effect value'
2947+
>>> order_mock.get_value()
2948+
'another side effect value'
2949+
2950+
>>> order_mock.get_value()
2951+
Traceback (most recent call last):
2952+
...
2953+
StopIteration

Lib/test/test_unittest/testmock/testmock.py

+67
Original file line numberDiff line numberDiff line change
@@ -245,6 +245,65 @@ class B(object):
245245
with mock.patch('builtins.open', mock.mock_open()):
246246
mock.mock_open() # should still be valid with open() mocked
247247

248+
def test_create_autospec_wraps_class(self):
249+
"""Autospec a class with wraps & test if the call is passed to the
250+
wrapped object."""
251+
result = "real result"
252+
253+
class Result:
254+
def get_result(self):
255+
return result
256+
class_mock = create_autospec(spec=Result, wraps=Result)
257+
# Have to reassign the return_value to DEFAULT to return the real
258+
# result (actual instance of "Result") when the mock is called.
259+
class_mock.return_value = mock.DEFAULT
260+
self.assertEqual(class_mock().get_result(), result)
261+
# Autospec should also wrap child attributes of parent.
262+
self.assertEqual(class_mock.get_result._mock_wraps, Result.get_result)
263+
264+
def test_create_autospec_instance_wraps_class(self):
265+
"""Autospec a class instance with wraps & test if the call is passed
266+
to the wrapped object."""
267+
result = "real result"
268+
269+
class Result:
270+
@staticmethod
271+
def get_result():
272+
"""This is a static method because when the mocked instance of
273+
'Result' will call this method, it won't be able to consume
274+
'self' argument."""
275+
return result
276+
instance_mock = create_autospec(spec=Result, instance=True, wraps=Result)
277+
# Have to reassign the return_value to DEFAULT to return the real
278+
# result from "Result.get_result" when the mocked instance of "Result"
279+
# calls "get_result".
280+
instance_mock.get_result.return_value = mock.DEFAULT
281+
self.assertEqual(instance_mock.get_result(), result)
282+
# Autospec should also wrap child attributes of the instance.
283+
self.assertEqual(instance_mock.get_result._mock_wraps, Result.get_result)
284+
285+
def test_create_autospec_wraps_function_type(self):
286+
"""Autospec a function or a method with wraps & test if the call is
287+
passed to the wrapped object."""
288+
result = "real result"
289+
290+
class Result:
291+
def get_result(self):
292+
return result
293+
func_mock = create_autospec(spec=Result.get_result, wraps=Result.get_result)
294+
self.assertEqual(func_mock(Result()), result)
295+
296+
def test_explicit_return_value_even_if_mock_wraps_object(self):
297+
"""If the mock has an explicit return_value set then calls are not
298+
passed to the wrapped object and the return_value is returned instead.
299+
"""
300+
def my_func():
301+
return None
302+
func_mock = create_autospec(spec=my_func, wraps=my_func)
303+
return_value = "explicit return value"
304+
func_mock.return_value = return_value
305+
self.assertEqual(func_mock(), return_value)
306+
248307
def test_explicit_parent(self):
249308
parent = Mock()
250309
mock1 = Mock(parent=parent, return_value=None)
@@ -622,6 +681,14 @@ def test_wraps_calls(self):
622681
real = Mock()
623682

624683
mock = Mock(wraps=real)
684+
# If "Mock" wraps an object, just accessing its
685+
# "return_value" ("NonCallableMock.__get_return_value") should not
686+
# trigger its descriptor ("NonCallableMock.__set_return_value") so
687+
# the default "return_value" should always be "sentinel.DEFAULT".
688+
self.assertEqual(mock.return_value, DEFAULT)
689+
# It will not be "sentinel.DEFAULT" if the mock is not wrapping any
690+
# object.
691+
self.assertNotEqual(real.return_value, DEFAULT)
625692
self.assertEqual(mock(), real())
626693

627694
real.reset_mock()

Lib/unittest/mock.py

+11-2
Original file line numberDiff line numberDiff line change
@@ -573,7 +573,7 @@ def __get_return_value(self):
573573
if self._mock_delegate is not None:
574574
ret = self._mock_delegate.return_value
575575

576-
if ret is DEFAULT:
576+
if ret is DEFAULT and self._mock_wraps is None:
577577
ret = self._get_child_mock(
578578
_new_parent=self, _new_name='()'
579579
)
@@ -1234,6 +1234,9 @@ def _execute_mock_call(self, /, *args, **kwargs):
12341234
if self._mock_return_value is not DEFAULT:
12351235
return self.return_value
12361236

1237+
if self._mock_delegate and self._mock_delegate.return_value is not DEFAULT:
1238+
return self.return_value
1239+
12371240
if self._mock_wraps is not None:
12381241
return self._mock_wraps(*args, **kwargs)
12391242

@@ -2785,9 +2788,12 @@ def create_autospec(spec, spec_set=False, instance=False, _parent=None,
27852788
if _parent is not None and not instance:
27862789
_parent._mock_children[_name] = mock
27872790

2791+
wrapped = kwargs.get('wraps')
2792+
27882793
if is_type and not instance and 'return_value' not in kwargs:
27892794
mock.return_value = create_autospec(spec, spec_set, instance=True,
2790-
_name='()', _parent=mock)
2795+
_name='()', _parent=mock,
2796+
wraps=wrapped)
27912797

27922798
for entry in dir(spec):
27932799
if _is_magic(entry):
@@ -2809,6 +2815,9 @@ def create_autospec(spec, spec_set=False, instance=False, _parent=None,
28092815
continue
28102816

28112817
kwargs = {'spec': original}
2818+
# Wrap child attributes also.
2819+
if wrapped and hasattr(wrapped, entry):
2820+
kwargs.update(wraps=original)
28122821
if spec_set:
28132822
kwargs = {'spec_set': original}
28142823

Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Fixed :func:`unittest.mock.create_autospec` to pass the call through to the wrapped object to return the real result.

0 commit comments

Comments
 (0)