Search code examples
pythonpython-3.xpython-mock

Is it possible to query a Python mock object for the return values of its calls?


Python 3 mock objects support being queried for arguments to their calls, is it possible to also query them for the values returned by their calls?

My particular scenario is that I mock tempfile.mkdtemp, but as a side effect call the real mkdtemp. I'd like to get hold of the created temporary directory in my test.

from unittest import mock
import shutil
import tempfile

from app import production_function


def mkdtemp(*args, **kwargs):
    dtemp = orig_mkdtemp(*args, **kwargs)
    return dtemp


orig_mkdtemp = tempfile.mkdtemp
patcher = mock.patch('tempfile.mkdtemp', name='tempfile.mkdtemp')
the_mock = patcher.start()
the_mock.side_effect = mkdtemp

# Call function under test
production_function()

assert the_mock.called
# Now, how to get the return value from the call to the_mock?

patcher.stop()

Solution

  • Unfortunately mock module don't store return value (I took a look by debugguer and there isn't any trace of it). You must store it before return the value of side_effect.

    You can use an object to take care of the dirty work. For instance a very base implementation can be something like that:

    class SideEffect():
        def __init__(self, n):
            self.values = iter(range(n))
            self.return_value = None
    
        def __call__(self):
            self.return_value = next(self.values)
            return self.return_value
    
    
    a = Mock()
    se = SideEffect(10)
    a.side_effect = se
    
    for x in range(10):
        v = a()
        assert v == se.return_value
        print("a()={}  return_value={}".format(v, se.return_value))
    

    If you want a more sophisticated side_effect that wrap a function and take care of arguments and exception an example could be:

    class GenericSideEffect():
        def __init__(self, f, *args, **kwargs):
            self.v_function = f
            self.args = args
            self.kwargs = kwargs
            self._return_value = Exception("Never Called")
    
        def __call__(self):
            try:
                self._return_value = self.v_function(*self.args, **self.kwargs)
                return self._return_value
            except Exception as e:
                self.return_value = e
                raise e
    
        @property
        def return_value(self):
            if isinstance(self._return_value, Exception):
                raise self._return_value
            return self._return_value
    

    Of course you can write it as decorator and preserve the signature, but I think that part is out of scope in that answer.