'Why can't dunder/magic methods be set in unittest.mock.Mock.configure_mock?

I was writing some unit test in python and needed to mock a very general collections.abc.Sized, so I set off creating a Mock with a __len__ method whose return value is any int I want.

from unittest.mock import Mock

sized = Mock(**{"__len__.return_value": 8})

Which failed with AttributeErrorgave: __len__ and produced the traceback at the end of the post, while the following works:

from unittest.mock import Mock

sized = Mock()
sized.__len__.return_value = 8

# or
from unittest.mock import MagicMock

sized = MagicMock(**{"__len__.return_value": 8})

Reading down the traceback we see that:

  1. CallableMixin's __init__ is called.
  2. kwargs passed to Mock's __init__ method get passed to Mock.configure_mock()
  3. the kwargs name is getattr()ed on the Mock itself
  4. Mock __getattr__ method raises an AttributeError if attribute name is magic, that is if '__%s__' % name[2:-2] == name.

This seems like a very arbitrary limitation and I wonder if there is a reason for Mock not to accept magic methods in Mock.configure_mock().

---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
Input In [2], in <cell line: 1>()
----> 1 Mock(**{"__len__.return_value": 0})

File ~/.pyenv/versions/3.10.3/lib/python3.10/unittest/mock.py:1086, in CallableMixin.__init__(self, spec, side_effect, return_value, wraps, name, spec_set, parent, _spec_state, _new_name, _new_parent, **kwargs)
   1082 def __init__(self, spec=None, side_effect=None, return_value=DEFAULT,
   1083              wraps=None, name=None, spec_set=None, parent=None,
   1084              _spec_state=None, _new_name='', _new_parent=None, **kwargs):
   1085     self.__dict__['_mock_return_value'] = return_value
-> 1086     _safe_super(CallableMixin, self).__init__(
   1087         spec, wraps, name, spec_set, parent,
   1088         _spec_state, _new_name, _new_parent, **kwargs
   1089     )
   1091     self.side_effect = side_effect

File ~/.pyenv/versions/3.10.3/lib/python3.10/unittest/mock.py:457, in NonCallableMock.__init__(self, spec, wraps, name, spec_set, parent, _spec_state, _new_name, _new_parent, _spec_as_instance, _eat_self, unsafe, **kwargs)
    454 __dict__['_mock_unsafe'] = unsafe
    456 if kwargs:
--> 457     self.configure_mock(**kwargs)
    459 _safe_super(NonCallableMock, self).__init__(
    460     spec, wraps, name, spec_set, parent,
    461     _spec_state
    462 )

File ~/.pyenv/versions/3.10.3/lib/python3.10/unittest/mock.py:625, in NonCallableMock.configure_mock(self, **kwargs)
    623 obj = self
    624 for entry in args:
--> 625     obj = getattr(obj, entry)
    626 setattr(obj, final, val)

File ~/.pyenv/versions/3.10.3/lib/python3.10/unittest/mock.py:636, in NonCallableMock.__getattr__(self, name)
    634         raise AttributeError("Mock object has no attribute %r" % name)
    635 elif _is_magic(name):
--> 636     raise AttributeError(name)
    637 if not self._mock_unsafe:
    638     if name.startswith(('assert', 'assret', 'asert', 'aseert', 'assrt')):

AttributeError: __len__


Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source