9

I want to mock an instance method. First I want to modify the arguments and then I want to call the original method.

I tried this:

import mock

class Foo(object):
    def my_method(data):
        print(data)

def wrapped_method(data):
    return Foo.my_method(data.replace('x', 'o'))

with mock.patch.object(Foo, 'my_method', wraps=wrapped_method):
    foo = Foo()
    foo.my_method('axe')  # should print "aoe"

But I get this exception:

/home/foo/bin/python /home/foo/src/wrap-instance-method.py
Traceback (most recent call last):
  File "/home/foo/src/wrap-instance-method.py", line 15, in <module>
    foo.my_method('axe')  # should print "aoe"
  File "/home/foo/local/lib/python2.7/site-packages/mock.py", line 955, in __call__
    return _mock_self._mock_call(*args, **kwargs)
  File "/home/foo/local/lib/python2.7/site-packages/mock.py", line 1024, in _mock_call
    return self._mock_wraps(*args, **kwargs)
.....
    return self._mock_wraps(*args, **kwargs)
  File "/home/foo/src/wrap-instance-method.py", line 10, in wrapped_method
    return Foo.my_method(data.replace('x', 'o'))
  File "/home/foo/local/lib/python2.7/site-packages/mock.py", line 955, in __call__
    return _mock_self._mock_call(*args, **kwargs)
  File "/home/foo/local/lib/python2.7/site-packages/mock.py", line 960, in _mock_call
    self.called = True
RuntimeError: maximum recursion depth exceeded while calling a Python object

Process finished with exit code 1

How can I call the original method which was wrapped without recursion exception?

1
  • Could you please tell me why you down-vote this question? Commented Aug 9, 2017 at 8:01

2 Answers 2

14

You replaced the Foo.my_method with a method that calls Foo.my_method, so yes, you'll get an infinite recursion.

If you must have the original method, you need to store it before you patch it:

def mock_method_factory(original):
    def wrapped_method(data):
        return original(data.replace('x', 'o'))
    return wrapped_method

with mock.patch.object(Foo, 'my_method', wraps=mock_method_factory(Foo.my_method)):
    # ...

However, wraps does not handle binding; original is an unbound function and wrapped_method won't be passed in self.

The mock library is perhaps not the best choice here. What you are essentially doing is apply a temporary decorator. Either subclass Foo or apply the decorator manually to Foo.my_method:

def mock_method_factory(original):
    def wrapped_method(self, data):
        return original(self, data.replace('x', 'o'))
    return wrapped_method

class MockedFoo(Foo):
    my_method = mock_method_factory(Foo.my_method)

foo = MockedFoo()
foo.my_method('axe')  # should print "aoe"

or

def mock_method_factory(original):
    def wrapped_method(self, data):
        return original(self, data.replace('x', 'o'))
    return wrapped_method

original = Foo.my_method
try:
    Foo.my_method = mock_method_factory(original)
    foo = Foo()
    foo.my_method('axe')  # should print "aoe"
finally:
    Foo.my_method = original
Sign up to request clarification or add additional context in comments.

7 Comments

I added "return wrappend_method" since I think it is needed. Now I get "TypeError: unbound method my_method() must be called with Foo instance as first argument (got str instance instead)"
@guettli: ah, yes, the replacement callable is not a method, so it won't be bound and no self is passed in.
Yes, not self is passed in. Sad. Is there an other way? I just want to do some kind of overwrite an instance method (comparable to inheritance and calling the parent method in the child method).
@guettli: added other ways that don't use unittest.mock.
Yes, the second solution (try..except (without mock)) works. I just need to add "self" here: def wrapped_method(self, data)
|
0

I encountered the same issue. Tried with Martijn's solution, but had the self problem. Solved by the following:

real_constructor = CLASS_NAME.__init__
def mock_constructor(self, **args):
    return real_constructor.(self=self, **args)

with mock.patch.object(Foo, '__init__', side_effect=mock_constructor):
    ...

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.