classification
Title: unitest.mock: Using autospec=True conflicts with 'wraps'
Type: behavior Stage:
Components: Library (Lib) Versions: Python 3.7, Python 3.6
process
Status: open Resolution:
Dependencies: Superseder:
Assigned To: Nosy List: John Villalovos, Lukas Anzinger, ezio.melotti, michael.foord, rbcollins, xtreak
Priority: normal Keywords: patch

Created on 2017-10-18 00:48 by John Villalovos, last changed 2019-12-02 08:29 by xtreak.

Files
File name Uploaded Description Edit
0001-bpo-31807-unittest.mock-Fix-common-use-of-autospec-T.patch Lukas Anzinger, 2017-11-15 15:29
Messages (3)
msg304549 - (view) Author: John Villalovos (John Villalovos) Date: 2017-10-18 00:48
If have autospec=True, then no ValueError is raised. If autospec=False or not defined, then the ValueError is raised.


import sys
from unittest import mock

def wrapped_func(value):
    raise ValueError(value)

@mock.patch('__main__.wrapped_func', autospec=True, wraps=wrapped_func)
def main(mock_wrap):

    wrapped_func("testing")

if '__main__' == __name__:
    sys.exit(main())
msg306273 - (view) Author: Lukas Anzinger (Lukas Anzinger) * Date: 2017-11-15 15:29
I can reproduce the problem and have analyzed it a bit. My use case is a bit different, I want to use autospec=True and wraps= so that I can mock unbound methods but return the result from the original method (see also https://docs.python.org/3/library/unittest.mock-examples.html#mocking-unbound-methods).

The problem in the mock code is that mock.return_value actually calls __get_return_value() which replaces the actual return value mock.DEFAULT (which is stored in self._mock_return_value) with a new child mock. When the mock is then called and _mock_call() is executed, the wrapped function is not executed because self._mock_return_value is not mock.DEFAULT anymore:


    if (self._mock_wraps is not None and
         self._mock_return_value is DEFAULT):
        return self._mock_wraps(*args, **kwargs)
    if ret_val is DEFAULT:
        ret_val = self.return_value
    return ret_val


Since self._mock_return_value is not DEFAULT anymore it doesn't matter if the mock wraps something and it will instead just return a new mock.

I think that the side effect of the assignment to self.return_value in _setup_func() is not intentional, i.e. the child mock should actually be created only if there is an outside access to return_value, but not when it is just wrapped in a function. The assignment can be made side effect free by assigning the internal attribute _mock_return_value (see attached patch). This solves the problem for me.
 
I've attached a patch that fixes the problem and doesn't seem to introduce a regression (all unittest.mock tests pass).

If somebody is interested in getting this merged, I'm happy to provide a regression test and everything else that is needed to get this merged.

Cheers,

Lukas
msg352287 - (view) Author: Lukas Anzinger (Lukas Anzinger) * Date: 2019-09-13 11:00
Hi,

I just wanted to ask what the status for this bug is.

I'm still interested in getting this merged and would be happy to help.

Lukas
History
Date User Action Args
2019-12-02 08:29:10xtreaksetnosy: + xtreak
2019-09-13 11:00:15Lukas Anzingersetmessages: + msg352287
2017-11-15 15:29:37Lukas Anzingersetfiles: + 0001-bpo-31807-unittest.mock-Fix-common-use-of-autospec-T.patch
versions: + Python 3.7
nosy: + Lukas Anzinger

messages: + msg306273

keywords: + patch
2017-10-20 18:50:30terry.reedysetnosy: + rbcollins, ezio.melotti
2017-10-20 18:50:09terry.reedysetnosy: + michael.foord
2017-10-18 05:07:19John Villalovossettitle: Using autospec=True conflicts with 'wraps' -> unitest.mock: Using autospec=True conflicts with 'wraps'
2017-10-18 00:48:15John Villalovoscreate