Francesco Montesano
Francesco Montesano

Reputation: 8658

unittest mock and multiple inheritance: TypeError: metaclass conflict

I have a package that has a dependency that is not pip installable. In order to be able to build the documentation, I am trying to mock the non-installable package using MagicMock.

However I stumbled across a problem with multiple inheritance: when one of the parent classes is a mock class I get:

TypeError: metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses

The following example illustrates the issue:

file: class_a.py

class A:
    pass

file: code.py

from class_a import A

class B:
    pass

class C(A, B):
    pass

file: test.py

import sys
from unittest import mock

# inspired by https://stackoverflow.com/a/37363830/1860757
MOCK_MODULES = ['class_a', ]
sys.modules.update((mod_name, mock.MagicMock()) for mod_name in MOCK_MODULES)

import code

code.C()

If I run python3 test.py I get the above exception. If I comment the line starting with sys.modules.update, it all behaves as expected.

Is there a way to mock modules or classes such that multiple inheritance continue working?

Upvotes: 2

Views: 794

Answers (2)

gotrunk
gotrunk

Reputation: 26

I know this is and old post but anyway. Looks like these lines are not necessary because the module class_a exists you don't need to mock it:

# MOCK_MODULES = ['class_a', ]
# sys.modules.update((mod_name, mock.MagicMock()) for mod_name in MOCK_MODULES)

Upvotes: 0

Francesco Montesano
Francesco Montesano

Reputation: 8658

I did some more research and tests and I have found a way, so I answer my own question for completeness. I don't know if it is a solution or a workaround. However the trick is to explicitly mock classes involved in multiple inheritance. The following does work as expected:

import sys
from unittest import mock


class _A:
    pass

MOCK_MODULES = ['class_a', ]
sys.modules.update((mod_name, mock.MagicMock()) for mod_name in MOCK_MODULES)
patcher = mock.patch('class_a.A', new=_A)
patcher.start()


import code


code.C()

patcher.stop()

If anyone in the future finds a new/better way, ping me and I will re-evaluate the accepted answer.

Upvotes: 4

Related Questions