Skip to content
7 changes: 7 additions & 0 deletions Doc/library/unittest.mock.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2036,6 +2036,11 @@ The full list of supported magic methods is:
.. versionchanged:: 3.8
Added support for ``__aenter__``, ``__aexit__``, ``__aiter__`` and ``__anext__``.

.. versionchanged:: 3.10
``__divmod__`` and ``__rdivmod`` now return a tuple of :class:`MagicMock` instances by default.
They previously returned a single :class:`MagicMock` instance.
This change was made to better support the unpacking synyax often used with :func:`divmod`.


The following methods exist but are *not* supported as they are either in use
by mock, can't be set dynamically, or can cause problems:
Expand Down Expand Up @@ -2106,6 +2111,8 @@ Methods and their defaults:
* ``__hash__``: default hash for the mock
* ``__str__``: default str for the mock
* ``__sizeof__``: default sizeof for the mock
* ``__divmod__``: ``(MagicMock(), MagicMock())``
* ``__rdivmod__``: ``(MagicMock(), MagicMock())``

For example:

Expand Down
14 changes: 14 additions & 0 deletions Lib/unittest/mock.py
Original file line number Diff line number Diff line change
Expand Up @@ -1941,11 +1941,25 @@ def method(self, /, *args, **kw):
'__del__'
}

def _get_divmod(self):
ret_val = self.__divmod__._mock_return_value
if ret_val is DEFAULT:
return (type(self)(), type(self)())
return ret_val

def _get_rdivmod(self):
ret_val = self.__rdivmod__._mock_return_value
if ret_val is DEFAULT:
return (type(self)(), type(self)())
return ret_val

_calculate_return_value = {
'__hash__': lambda self: object.__hash__(self),
'__str__': lambda self: object.__str__(self),
'__sizeof__': lambda self: object.__sizeof__(self),
'__fspath__': lambda self: f"{type(self).__name__}/{self._extract_mock_name()}/{id(self)}",
'__divmod__': _get_divmod,
'__rdivmod__': _get_rdivmod,
}

_return_values = {
Expand Down
73 changes: 65 additions & 8 deletions Lib/unittest/test/testmock/testmagicmethods.py
Original file line number Diff line number Diff line change
Expand Up @@ -475,20 +475,77 @@ def test_matmul(self):
m @= 24
self.assertEqual(m, 24)


def test_divmod_and_rdivmod(self):
# The behaviour of divmod and rdivmod has been changed in the course of
# fixing issue34716, such that now a tuple of MagicMock instances is
# returned from both of these methods by default. The previous
# behaviour was to return a single MagicMock instance.

def _assert_is_2_tuple(obj, klass):
self.assertIsInstance(obj, tuple)
self.assertEqual(len(obj), 2)
for elem in obj:
self.assertIsInstance(elem, klass)

# Test divmod
m = MagicMock()
self.assertIsInstance(divmod(5, m), MagicMock)
foo = divmod(m, 2)
_assert_is_2_tuple(foo, MagicMock)

# Test __divmod__ directly
foo_direct = m.__divmod__(2)
_assert_is_2_tuple(foo_direct, MagicMock)

# Test changing the return value of divmod
m.__divmod__.return_value = (2, 1)
self.assertEqual(divmod(m, 2), (2, 1))
self.assertEqual(divmod(m, None), (2, 1))

# Test rdivmod
m = MagicMock()
foo = divmod(2, m)
self.assertIsInstance(foo, MagicMock)
foo_direct = m.__divmod__(2)
self.assertIsInstance(foo_direct, MagicMock)
bar = divmod(m, 2)
self.assertIsInstance(bar, MagicMock)
bar = divmod(2, m)
_assert_is_2_tuple(bar, MagicMock)

# Test __rdivmod__ directly
bar_direct = m.__rdivmod__(2)
self.assertIsInstance(bar_direct, MagicMock)
_assert_is_2_tuple(bar_direct, MagicMock)

# Test changing the return value of rdivmod
m.__rdivmod__.return_value = (2, 1)
self.assertEqual(divmod(2, m), (2, 1))
self.assertEqual(divmod(None, m), (2, 1))

# Test what happens if two MagicMocks are passed into divmod

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about if a subclass of MagicMock is passed - we expect to get two instances of the subclass out right?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes - will add a test for this, thanks :)

a = MagicMock()
b = MagicMock()
baz = divmod(a, b)
_assert_is_2_tuple(baz, MagicMock)

# Test the behaviour of divmod with two MagicMock instances when one
# has a return value set for __divmod__.
# __divmod__ on the first operand takes precendence over __rdivmod__ on
# the second operand, so setting a return value for __divmod__ on the
# first operand is expected to change what is returned from
# divmod(first, second)
a.__divmod__.return_value = (2, 1)
self.assertEqual(divmod(a, b), (2,1))
qux = divmod(b, a)
_assert_is_2_tuple(qux, MagicMock)

# Test that if MagicMock is subclassed, divmod and rdivmod return a
# tuple of the subclass instances, rather than of MagicMock instances
class MagicMockSubClass(MagicMock):
pass

sub_a = MagicMockSubClass()
foo = divmod(sub_a, 2)
_assert_is_2_tuple(foo, MagicMockSubClass)

sub_a = MagicMockSubClass()
bar = divmod(2, sub_a)
_assert_is_2_tuple(bar, MagicMockSubClass)


# http://bugs.python.org/issue23310
# Check if you can change behaviour of magic methods in MagicMock init
Expand Down
1 change: 1 addition & 0 deletions Misc/ACKS
Original file line number Diff line number Diff line change
Expand Up @@ -1433,6 +1433,7 @@ Martin Richard
Jan Pieter Riegel
Armin Rigo
Arc Riley
Jackson Riley
Nicholas Riley
Jean-Claude Rimbault
Vlad Riscutia
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
:mod:`unittest.mock`'s :func:`MagicMock().__divmod__` and :func:`__rdivmod__` now return a tuple of `MagicMock` instances by default.
They previously returned a single `MagicMock` instance.
This change was made to better support the unpacking syntax often used with :func:`divmod`.