2

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.

Intrastellar Explorer
  • 3,005
  • 9
  • 52
  • 119

1 Answers1

2

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!

blhsing
  • 91,368
  • 6
  • 71
  • 106