Search code examples
pythonpython-3.xmockingstub

Why mock patch not works as expected?


b.py:

from unittest.mock import patch

def hello():
    print("hello")
    return 1

@patch("b.hello", return_value="wow")
def fun(mock_hello):
    print(hello())

print("start")
fun()
print("end")

I use python3:

pie@pie:~$ python3 --version
Python 3.6.9

For above code, I expect to get next because I have mocked hello:

start
wow
end

But actually, I got next:

pie@pie:~$ python3 b.py
start
start
wow
end
hello
1
end

I have totally confused by the mock behavior, what happened?


Solution

  • Update the answer in case anyone interested, the root cause is patch target and patch in same file. There won't have any issue if not in the same file.

    From the sourcecode of patch function in unittest/mock.py, we could see in fact it use __import__ to import the target:

    patch -> getter, attribute = _get_target(target) -> getter = lambda: _importer(target) -> thing = __import__(import_path)

    So above code in question behave as next:

    1. As top level script b.py start, it first print start
    2. As top level script, it call fun(), at this point, the patch will use __import__ to import b.hello. So b.py now act as a python module which be executed again. As a python module, it print start.
    3. As python module, it call fun, as module already imported, so this won't start another import. As a module run, hello() really means b.hello(), which already patched, it should returns wow. So, wow now be printed.
    4. As python module, it finally print end.
    5. Now python module executed finish, b.py as a top level script continue run hello(), but now the patch indeed patched b.hello, not hello, so it still print hello & 1.
    6. Finally, as python top level script it print end.