Substitute a mock object for the metaclass of a class
Ben Finney
ben+python at benfinney.id.au
Sun Mar 12 20:52:18 EDT 2017
How can I override the metaclass of a Python class, with a
`unittest.mock.MagicMock` instance instead?
I have a function whose job involves working with the metaclass of an
argument::
# lorem.py
class Foo(object):
pass
def quux(existing_class):
…
metaclass = type(existing_class)
new_class = metaclass(…)
The unit tests for this function will need to assert that the calls to
the metaclass go as expected, *without* actually calling a real class
object.
To write a unit test for this function, I want to temporarily override
the metaclass of some classes in the system with a mock object instead.
This will allow, for example, making assertions about how the metaclass
was called, and ensuring no unwanted side effects.
# test_lorem.py
import unittest
import unittest.mock
import lorem
class stub_metaclass(type):
def __new__(metaclass, name, bases, namespace):
return super().__new__(metaclass, name, bases, namespace)
class quux_TestCase(unittest.TestCase):
@unittest.mock.patch.object(
lorem.Foo, '__class__', side_effect=stub_metaclass)
def test_calls_expected_metaclass_with_class_name(
self,
mock_foo_metaclass,
):
lorem.quux(lorem.Foo)
mock_foo_metaclass.assert_called_with(
'Foo', unittest.mock.ANY, unittest.mock.ANY)
When I try to mock the `__class__` attribute of an existing class,
though, I get this error::
File "/usr/lib/python3/dist-packages/mock/mock.py", line 1500, in start
result = self.__enter__()
File "/usr/lib/python3/dist-packages/mock/mock.py", line 1460, in __enter__
setattr(self.target, self.attribute, new_attr)
TypeError: __class__ must be set to a class, not 'MagicMock' object
This is telling me that `unittest.mock.patch` is attempting to set the
`__class__` attribute temporarily to a `MagicMock` instance, as I want;
but Python is refusing that with a `TypeError`.
But placing a mock object as the metaclass (the class's `__class__`
attribute) is exactly what I'm trying to do, *in order that* the mock
object will do all that it does: record calls, pretend valid behaviour,
etc.
How can I set a mock object in place of the class's `__class__`
attribute, in order to instrument my code for testing metaclass
operation?
--
\ “The problem with television is that the people must sit and |
`\ keep their eyes glued on a screen: the average American family |
_o__) hasn't time for it.” —_The New York Times_, 1939 |
Ben Finney
More information about the Python-list
mailing list