tlayton
tlayton

Reputation: 13

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.

Upvotes: 1

Views: 46

Answers (1)

user2357112
user2357112

Reputation: 282042

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.

Upvotes: 2

Related Questions