python mock - patching a method without obstructing implementation

PythonMocking

Python Problem Overview


Is there a clean way to patch an object so that you get the assert_call* helpers in your test case, without actually removing the action?

For example, how can I modify the @patch line to get the following test passing:

from unittest import TestCase
from mock import patch


class Potato(object):
    def foo(self, n):
        return self.bar(n)

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


class PotatoTest(TestCase):

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

I could probably hack this together using side_effect, but I was hoping there would be a nicer way which works the same way on all of functions, classmethods, staticmethods, unbound methods, etc.

Python Solutions


Solution 1 - Python

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)

Solution 2 - Python

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

Solution 3 - Python

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)

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionwimView Question on Stackoverflow
Solution 1 - PythonfalsetruView Answer on Stackoverflow
Solution 2 - PythonwimView Answer on Stackoverflow
Solution 3 - PythonIntrastellar ExplorerView Answer on Stackoverflow