Search code examples
pythonpython-3.xpython-unittest.mockmagicmock

Monkeypatching `__call__` on MagicMock


Let's say I define a helper method to monkeypatch a simple modification into the __call__ behavior of an existing object:

def and_print_on_call(instance):
  class AndPrintOnCall(type(instance)):
    def __call__(self, *args, **kwarg):
      print('printed!')
      return super().__call__(*args, **kwarg)

  instance.__class__ = AndPrintOnCall

If I apply this to a typical class instance, it works as intended:

class Foo:
  def __call__(self):
    print('foo!')
foo = Foo()

and_print_on_call(foo)
foo()  # Prints "printed!" then "foo!"

But if I apply it to a MagicMock instance, it doesn't:

foo = unittest.mock.MagicMock()
foo.side_effect = lambda: print('foo!')

and_print_on_call(foo)
foo()  # Prints only "foo!"

Why? What is MagicMock doing special, that calling an instance of it apparently doesn't reference __class__.__call__ like it would for a typical class?


To provide some broader context: I'm attempting to create a deep-copying mock, in order to handle mutable arguments (similar to what's discussed here, though none of those provided solutions quite meet my particular needs). In the real implementation, the print('printed!') statement would instead be the copy logic.


Solution

  • Mocks intercept __class__ assignment:

    def __setattr__(self, name, value):
        ...
        elif name == '__class__':
            self._spec_class = value
            return
    

    so your instance.__class__ = AndPrintOnCall doesn't actually work.