[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