29
class A():
    def tmp(self):
        print("hi")

def b(a):
    a.tmp()

To check if tmp method is called in b, the recommended way is

a = A()
a.tmp = MagicMock()
b(a)
a.tmp.assert_called()

But tmp here is being mocked away and is not resulting in a "hi" getting printed.

I would want my unit test to check if method tmp is called without mocking it away.

Is this possible?

I know this is not a standard thing to expect when writing unitests. But my use case (which is bit tricky) requires this.

Patrick Haugh
  • 59,226
  • 13
  • 88
  • 96
claudius
  • 1,112
  • 1
  • 10
  • 23

3 Answers3

41

You can set the Mock.side_effect to be the original method.

from unittest.mock import MagicMock

class A():
    def tmp(self):
        print("hi")

def b(a):
    a.tmp()

a = A()
a.tmp = MagicMock(side_effect=a.tmp)
b(a)
a.tmp.assert_called()

When side_effect is a function (or a bound method in this case, which is a kind of function), calling the Mock will also call the side_effect with the same arguments.

The Mock() call will return whatever the side_effect returns, unless it returns the unnittest.mock.DEFAULT singleton. Then it will return Mock.return_value instead.

Patrick Haugh
  • 59,226
  • 13
  • 88
  • 96
2

Or you can decorate the method to test:

def check_called(fun):
    def wrapper(self, *args, **kw):
        attrname = "_{}_called".format(fun.__name__)
        setattr(self, attrname, True)
        return fun(self, *args, **kw)
    return wrapper


a = A()
a.tmp = check_called(a.tmp)
b(a)
assert(getattr(a, "_tmp_called", False))

but MagicMock's side_effect is definitly a better solution if you're already using Mock ;)

bruno desthuilliers
  • 75,974
  • 6
  • 88
  • 118
0

In addition to @Patrick Haugh's answer, you can also pass the function to the wraps argument (and it seems more semantically correct to me).

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).

a = A()
a.tmp = MagicMock(wraps=a.tmp)
b(a)
a.tmp.assert_called()
hashlash
  • 897
  • 8
  • 19