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