[TIP] Substitute a mock object for the metaclass of a class
Ben Finney
ben+python at benfinney.id.au
Mon Mar 13 12:19:11 PDT 2017
Howdy all,
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.
Note: The test case *does not care* about the metaclass's behaviour; it
cares that `quux` retrieves that metaclass (using
`type(existing_class)`) and calls the metaclass with the correct
arguments.
So to write a unit test for this function, I want to pass a class object
whose metaclass is a mock object instead::
# 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,
):
expected_name = 'Foo'
expected_bases = …
expected_namespace = …
lorem.quux(lorem.Foo)
mock_foo_metaclass.assert_called_with(
expected_name, expected_bases, expected_namespace)
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 is exactly what I'm trying to
do: put a `unittest.mock.MagicMock` instance in the `__class__`
attribute *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 `Foo` class's metaclass (its
`__class__` attribute), in order to instrument `Foo` and test that my
code uses `Foo`'s metaclass correctly?
--
\ “Program testing can be a very effective way to show the |
`\ presence of bugs, but is hopelessly inadequate for showing |
_o__) their absence.” —Edsger W. Dijkstra |
Ben Finney
More information about the testing-in-python
mailing list