In Python, classes are actually instances of the type
class. A class
statement like this:
class c(a):
@staticmethod
def x():
return 1
is really syntactic sugar of calling type
with the name of the class, the base classes and the class members:
c = type('c', (a,), {'x': staticmethod(lambda: 1)})
The above statement would go through the given base classes and call the __new__
method of the type of the first base class with the __new__
method defined, which in this case is a
. The return value gets assigned to c
to become a new class.
Normally, a
would be an actual class--an instance of type
or a subclass of type
. But in this case, a
is not an instance of type
, but rather an instance of MagicMock
, so MagicMock.__new__
, instead of type.__new__
, is called with these 3 arguments.
And here lies the problem: MagicMock
is not a subclass of type
, so its __new__
method is not meant to take the same arguments as type.__new__
. And yet, when MagicMock.__new__
is called with these 3 arguments, it takes them without complaint anyway because according to the signature of MagicMock
's constructor (which is the same as Mock
's):
class unittest.mock.Mock(spec=None, side_effect=None,
return_value=DEFAULT, wraps=None, name=None, spec_set=None,
unsafe=False, **kwargs)
MagicMock.__new__
would assign the 3 positional arguments as spec
, side_effect
and return_value
, respectively. As you now see, the first argument, the class name ('c'
in this case), an instance of str
, becomes spec
, which is why your class c
becomes an instance of MagicMock
with a spec
of str
.
The solution
Luckily, a magic method named __mro_entries__
was introduced since Python 3.7 that can solve this problem by providing a non-class base class with a substitute base class, so that when a
, an instance of MagicMock
, is used as a base class, we can use __mro_entries__
to force its child class to instead use a
's class, MagicMock
(or SubclassableMagicMock
in the following example), as a base class:
from unittest.mock import MagicMock
class SubclassableMagicMock(MagicMock):
def __mro_entries__(self, bases):
return self.__class__,
so that:
a = SubclassableMagicMock()
class b():
@staticmethod
def x():
return 1
class c(a):
@staticmethod
def x():
return 1
print(a)
print(b)
print(c)
print(a.x())
print(b.x())
print(c.x())
outputs:
<SubclassableMagicMock id='140127365021408'>
<class '__main__.b'>
<class '__main__.c'>
<SubclassableMagicMock name='mock.x()' id='140127351680080'>
1
1
Demo: https://replit.com/@blhsing/HotAcademicCases