[3.11] gh-75988: Fix issues with autospec ignoring wrapped object (GH-115223) (#117124)

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

(cherry picked from commit 735fc2cbbc)
This commit is contained in:
infohash 2024-03-22 15:18:41 +05:30 committed by GitHub
parent 0ec8561574
commit 51da1ddefc
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 198 additions and 2 deletions

View file

@ -2780,3 +2780,123 @@ Sealing mocks
>>> mock.not_submock.attribute2 # This won't raise.
.. versionadded:: 3.7
Order of precedence of :attr:`side_effect`, :attr:`return_value` and *wraps*
----------------------------------------------------------------------------
The order of their precedence is:
1. :attr:`~Mock.side_effect`
2. :attr:`~Mock.return_value`
3. *wraps*
If all three are set, mock will return the value from :attr:`~Mock.side_effect`,
ignoring :attr:`~Mock.return_value` and the wrapped object altogether. If any
two are set, the one with the higher precedence will return the value.
Regardless of the order of which was set first, the order of precedence
remains unchanged.
>>> from unittest.mock import Mock
>>> class Order:
... @staticmethod
... def get_value():
... return "third"
...
>>> order_mock = Mock(spec=Order, wraps=Order)
>>> order_mock.get_value.side_effect = ["first"]
>>> order_mock.get_value.return_value = "second"
>>> order_mock.get_value()
'first'
As ``None`` is the default value of :attr:`~Mock.side_effect`, if you reassign
its value back to ``None``, the order of precedence will be checked between
:attr:`~Mock.return_value` and the wrapped object, ignoring
:attr:`~Mock.side_effect`.
>>> order_mock.get_value.side_effect = None
>>> order_mock.get_value()
'second'
If the value being returned by :attr:`~Mock.side_effect` is :data:`DEFAULT`,
it is ignored and the order of precedence moves to the successor to obtain the
value to return.
>>> from unittest.mock import DEFAULT
>>> order_mock.get_value.side_effect = [DEFAULT]
>>> order_mock.get_value()
'second'
When :class:`Mock` wraps an object, the default value of
:attr:`~Mock.return_value` will be :data:`DEFAULT`.
>>> order_mock = Mock(spec=Order, wraps=Order)
>>> order_mock.return_value
sentinel.DEFAULT
>>> order_mock.get_value.return_value
sentinel.DEFAULT
The order of precedence will ignore this value and it will move to the last
successor which is the wrapped object.
As the real call is being made to the wrapped object, creating an instance of
this mock will return the real instance of the class. The positional arguments,
if any, required by the wrapped object must be passed.
>>> order_mock_instance = order_mock()
>>> isinstance(order_mock_instance, Order)
True
>>> order_mock_instance.get_value()
'third'
>>> order_mock.get_value.return_value = DEFAULT
>>> order_mock.get_value()
'third'
>>> order_mock.get_value.return_value = "second"
>>> order_mock.get_value()
'second'
But if you assign ``None`` to it, this will not be ignored as it is an
explicit assignment. So, the order of precedence will not move to the wrapped
object.
>>> order_mock.get_value.return_value = None
>>> order_mock.get_value() is None
True
Even if you set all three at once when initializing the mock, the order of
precedence remains the same:
>>> order_mock = Mock(spec=Order, wraps=Order,
... **{"get_value.side_effect": ["first"],
... "get_value.return_value": "second"}
... )
...
>>> order_mock.get_value()
'first'
>>> order_mock.get_value.side_effect = None
>>> order_mock.get_value()
'second'
>>> order_mock.get_value.return_value = DEFAULT
>>> order_mock.get_value()
'third'
If :attr:`~Mock.side_effect` is exhausted, the order of precedence will not
cause a value to be obtained from the successors. Instead, ``StopIteration``
exception is raised.
>>> order_mock = Mock(spec=Order, wraps=Order)
>>> order_mock.get_value.side_effect = ["first side effect value",
... "another side effect value"]
>>> order_mock.get_value.return_value = "second"
>>> order_mock.get_value()
'first side effect value'
>>> order_mock.get_value()
'another side effect value'
>>> order_mock.get_value()
Traceback (most recent call last):
...
StopIteration