44

I want to use patch to record all function calls made to a function in a class for a unittest, but need the original function to still run as expected. I created a dummy code example below:

from mock import patch

class A(object):
    def __init__(self):
        self._a = 1

class B(A):
    def __init__(self):
        super(B, self).__init__() # TypeError: super() argument 1 must be type, not MagicMock
        self._b = 11

    def bar(self, b):
        self._b = self._b + 1 + b

    def foo(self, b):
        self.bar(b)

class MockB(B):
    def foo(self, b):
        super(MockB, self).foo(self, b)

@patch('main.B')
def main(b_class):
    b_class.side_effect = MockB

    b = B()

    print b._b # 11
    b.foo(0)
    print b._b # 12

main()

In my case, the instance of the class b = B() is not actually in the main function but in another module, so I can't Mock the instance. I need it to generically be a decorator for all instances of B.

Summary: I am not sure how to individually mock the class method on it's own, but still call the original method. After, I want to use something like call_args_list where I can see all calls made to foo().

mojo1mojo2
  • 1,062
  • 1
  • 18
  • 28

3 Answers3

25

I think you are looking for the wraps Mock parameter. Search the official documentation for wraps. Accessing attributes returns a mock object. Calling methods gives the real method result instead, if a return value is not configured for the mock.

progmatico
  • 4,714
  • 1
  • 16
  • 27
  • 10
    A code snippet with an example wouldn't hurt. https://stackoverflow.com/a/36724642/8843585 – Ramon Dias Aug 17 '22 at 23:14
  • 2
    This works exactly how I wanted it to, e.g., `mock.patch('os.remove', wraps=os.remove)`. This way, I have mock with which I can call `assert_called_once_with()`, etc., and `os.remove()` still gets called as is. – Chris Wong Oct 29 '22 at 02:57
3

More details can be found here: https://stackoverflow.com/a/61963740/1731460

import mock
from contextlib import contextmanager


@contextmanager
def mock_patch_method_original(mock_path, original_method, results):
    results = iter(results)

    def side_effect(self, *args, **kwargs):
        value = next(results)
        if value == '__original__':
            side_effect.self = self
            return original_method(self, *args, **kwargs)
        else:
            return value

    patcher = mock.patch(mock_path, autospec=True, side_effect=side_effect)
    yield patcher.start()
    patcher.stop()
pymen
  • 5,737
  • 44
  • 35
2

Just keep a reference to your original, non-mocked/patched instance around.

I had an issue that I was patching ctypes.windll.winmm.mciSendStringW, but I still wanted to be able to access that original function. Initially, I had tried:

from ctypes import windll
originalWindll = windll

# Later on trying to call the original mciSendStringW when it's patched...
originalWindll.winmm.mciSendStringW(...)  # Didn't work! Called the patched version of the function!

The correct thing to do was actually this... since it was the function being mocked/patched, it was the function that I needed to keep a reference to.

from ctypes import windll
originalMCISendStringW = windll.winmm.mciSendStringW

# Later on trying to call the original mciSendStringW when it's patched...
originalMCISendStringW(...)  # Works! Calls the original, non-patched version of this function!
Dharman
  • 30,962
  • 25
  • 85
  • 135
ArtOfWarfare
  • 20,617
  • 19
  • 137
  • 193