I have something like this:
from unittest.mock import MagicMock
class A:
pass
class B(A):
pass
mock_B = MagicMock(spec_set=B)
assert issubclass(mock_B, A) # TypeError: issubclass() arg 1 must be a class
How can I get this to pass?
isinstance and Mocking had a lot of stuff various answers, but from there I can't figure it out.
The problem here is that you want to treat mock_B
, an instance of MagicMock
, as a class, but an object in Python can only be considered as a class if it is an instance of type
, which mock_B
is simply not.
So instead of trying to make a Mock
object class-like, which is technically impossible since Python does not allow dynamically changing the type of an object, a workaround would be to make a class Mock
-like. To do that, we can create a class with the same name and base classes as the class you want to mock, but then delegate all of its attribute lookups to a Mock
object by making the __getattribute__
method of the class' metaclass perform such a delegation. This way, the new class passes the issubclass
check easily because it is really a subclass of the intended base class A
, and yet it also behaves like a Mock
object because all attribute lookups are delegated to one.
However, one problem with delegating all attribute lookups to a Mock
object is that the resulting "class" would not behave like a class as the Mock
object has no class-like attributes. We can solve this by defaulting attributes that aren't available in the Mock
object to the class being mocked instead:
from unittest.mock import MagicMock
def mock_class(cls):
class meta(type):
def __getattribute__(self, name):
try:
return getattr(mock, name)
except AttributeError:
return getattr(cls, name)
mock = MagicMock(spec_set=cls)
return meta(cls.__name__, cls.__bases__, {})
so that:
class A:
pass
class B(A):
def foo(self):
pass
mock_B = mock_class(B)
assert issubclass(mock_B, A)
print(mock_B)
print(mock_B.foo)
print(mock_B.__name__)
print(mock_B.__bases__)
passes the issubclass
assertion and outputs:
<class '__main__.B'>
<MagicMock name='mock.foo' id='1970990059024'>
B
(<class '__main__.A'>,)
Demo: Try it online!