On Mon, 13 Mar 2017 at 00:52 Ben Finney <ben+pyt...@benfinney.id.au> wrote:
> How can I override the metaclass of a Python class, with a > `unittest.mock.MagicMock` instance instead? > At first I misunderstood what you were looking for, and was about to reply to the effect of "you're too late, the metaclass has already been called so this doesn't make sense", but I see that you're actually asking for something that's a bit interesting... > 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. > The simple, lazy option would be to patch `type` to return your mocked metaclass. i.e. ``` from unittest.mock import patch import lorem @patch('lorem.type') def test_things(mocktype): lorem.quux(metameta.Foo()) lorem.return_value.assert_called_with() ``` Don't try and patch `type` from `builtins`. I did, and things go bad because, unsurprisingly, `mock` calls `type` quite a lot internally :). > # 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 add the stub_metaclass side_effect in to my code I get `TypeError: __new__() missing 2 required positional arguments: 'bases' and 'namespace'` ... which seems quite reasonable, and I expect you're in a better position to figure out how to handle that (the side effect may actually need to be a wrapper around stub_metaclass which injects something suitable). <rest of original snipped> -- -- Matt Wheeler http://funkyh.at -- https://mail.python.org/mailman/listinfo/python-list