'python mock - patching a method without obstructing implementation

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.



Solution 1:[1]

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 2:[2]

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)

Solution 3:[3]

You are describing the same question than Python mock: wrap instance method. My solution in https://stackoverflow.com/a/72446339/9230828 can be applied as following: Put wrap_object somewhere, e.g. into wrap_object.py:

# Copyright (C) 2022, Benjamin Drung <[email protected]>
#
# Permission to use, copy, modify, and/or distribute this software for any
# purpose with or without fee is hereby granted, provided that the above
# copyright notice and this permission notice appear in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.

import contextlib
import typing
import unittest.mock

@contextlib.contextmanager
def wrap_object(
    target: object, attribute: str
) -> typing.Generator[unittest.mock.MagicMock, None, None]:
    """Wrap the named member on an object with a mock object.

    wrap_object() can be used as a context manager. Inside the
    body of the with statement, the attribute of the target is
    wrapped with a :class:`unittest.mock.MagicMock` object. When
    the with statement exits the patch is undone.

    The instance argument 'self' of the wrapped attribute is
    intentionally not logged in the MagicMock call. Therefore
    wrap_object() can be used to check all calls to the object,
    but not differentiate between different instances.
    """
    mock = unittest.mock.MagicMock()
    real_attribute = getattr(target, attribute)

    def mocked_attribute(self, *args, **kwargs):
        mock.__call__(*args, **kwargs)
        return real_attribute(self, *args, **kwargs)

    with unittest.mock.patch.object(target, attribute, mocked_attribute):
        yield mock

Then you can write following unit test:

from unittest import TestCase

from wrap_object import wrap_object


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

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


class PotatoTest(TestCase):

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

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2 Intrastellar Explorer
Solution 3 Benjamin Drung