python mock - patching a method without obstructing implementation

This answer address the additional requirement mentioned in the bounty from user Quuxplusone:

The important thing for my use-case is that it work with @patch.mock, i.e. that it not require me to insert any code in between my constructing of the instance of Potato (spud in this example) and my calling of spud.foo. I need spud to be created with a mocked-out foo method from the get-go, because I do not control the place where spud is created.

The use case described above could be achieved without too much trouble by using a decorator:

import unittest
import unittest.mock  # Python 3

def spy_decorator(method_to_decorate):
    mock = unittest.mock.MagicMock()
    def wrapper(self, *args, **kwargs):
        mock(*args, **kwargs)
        return method_to_decorate(self, *args, **kwargs)
    wrapper.mock = mock
    return wrapper

def spam(n=42):
    spud = Potato()
    return spud.foo(n=n)

class Potato(object):

    def foo(self, n):
        return self.bar(n)

    def bar(self, n):
        return n + 2

class PotatoTest(unittest.TestCase):

    def test_something(self):
        foo = spy_decorator(Potato.foo)
        with unittest.mock.patch.object(Potato, 'foo', foo):
            forty_two = spam(n=40)
        foo.mock.assert_called_once_with(n=40)
        self.assertEqual(forty_two, 42)


if __name__ == '__main__':
    unittest.main()

If the method replaced accepts mutable arguments which are modified under test, you might wish to initialize a CopyingMock* in place of the MagicMock inside the spy_decorator.

*It's a recipe taken from the docs which I've published on PyPI as copyingmock lib


The OP makes this request:

I could probably hack this together using side_effect

For those who don't mind using side_effect, here's a solution with a few pros:

  • Uses decorator syntax
  • Patches an unbound method, which I find is more versatile
    • Requires inclusion of the instance in the assertion
class PotatoTest(TestCase):

    @patch.object(Potato, 'foo', side_effect=Potato.foo, autospec=True)
    def test_something(self, mock):
        spud = Potato()
        forty_two = spud.foo(n=40)
        mock.assert_called_once_with(spud, n=40)
        self.assertEqual(forty_two, 42)

Similar solution with yours, but using wraps:

def test_something(self):
    spud = Potato()
    with patch.object(Potato, 'foo', wraps=spud.foo) as mock:
        forty_two = spud.foo(n=40)
        mock.assert_called_once_with(n=40)
    self.assertEqual(forty_two, 42)

According to the documentation:

wraps: Item for the mock object to wrap. If wraps is not None then calling the Mock will pass the call through to the wrapped object (returning the real result). Attribute access on the mock will return a Mock object that wraps the corresponding attribute of the wrapped object (so attempting to access an attribute that doesn’t exist will raise an AttributeError).


class Potato(object):

    def spam(self, n):
        return self.foo(n=n)

    def foo(self, n):
        return self.bar(n)

    def bar(self, n):
        return n + 2


class PotatoTest(TestCase):

    def test_something(self):
        spud = Potato()
        with patch.object(Potato, 'foo', wraps=spud.foo) as mock:
            forty_two = spud.spam(n=40)
            mock.assert_called_once_with(n=40)
        self.assertEqual(forty_two, 42)

Tags:

Python

Mocking