Mock.patch in python unittest could work for two paths

Viewed 686

I have a dir which has such structure:

├── aaa.py
├── src
│   └── subsrc
│       ├── else.py
│       └── util.py  (there is a "foo" function")
└── tests
    ├── __init__.py
    └── unittests
        ├── __init__.py
        └── test_aaa.py

so "aaa.py", "tests" dir and "src" dir are in project root. and in "test_aaa.py", I use mock to mock function in "util.py":

from src.subsrc.util import foo
import pytest
from unittest import mock

@mock.patch("src.subsrc.util.foo")
def test_foo(mock):
    mock.return_value = 111

and then I run python3.7 -m pytest inside "unittests" dir, it worked. This makes sense to me since pytest will find the first dir without __init__.py and then add it to PATH(in this case project root dir will be added) so it could find "src.subsrc.util.foo".

But then I made a small change to "test_aaa.py", in its "mock.patch", I added "aaa" at the beginning:

from src.subsrc.util import foo
import pytest
from unittest import mock

@mock.patch("aaa.src.subsrc.util.foo")
def test_foo(mock):
    mock.return_value = 111

it still worked, "aaa.py" is an executable, in "aaa.py":

#!python3.7
from src.subsrc.else import other

if __name__ = "__main__":
   # ...
   pass

I am very confused why @mock.patch("aaa.src.subsrc.util.foo") also worked, is Python so smart that it could ignore 'aaa' then go "src.subsrc.." to find what it needs? Thanks!

update: I suspect if because "aaa.py"'s name is special so I changed it to different names, but it still worked. Like I change it to "bbb.py", then in mock.patch, "aaa.src..." does not work but "bbb.src..." still worked. So I am sure "mock.patch" find this executable first.

update: I guess it could be related to how "mock.patch()" works?

1 Answers

Your example seems to be a bit too stripped-down, but I'll try to expand it in order to explain. When reading about mocks in Python, you will often encounter the phrase "mock it where it's used", which isn't really helpful if you are new to the topic (but here's an excellent article on this concept).

In your test_aaa.py you will probably want to test some functionality of your aaa.py module, which may call some function from src/subsrc/util.py. After importing your foo() function in the aaa.py module, that's the exact location where you should point @mock.patch to: @mock.patch("aaa.foo"). By doing this, your mock will have access to all invocations of foo() in the functions you are about to test, namely aaa.do_something(). I've expanded your example as follows:

# aaa.py

from src.subsrc.util import foo

def do_something():
    return foo()

if __name__ == "__main__":
    value = do_something()
    print(f"value is {value}")
# src/subsrc/util.py

def foo():
    return 222
# tests/unittests/test_aaa.py

from unittest import mock
from aaa import do_something

@mock.patch("aaa.foo")
def test_foo(foo_mocked):
    foo_mocked.return_value = 111
    value = do_something()
    assert value == 111

When executing this like python aaa.py, I get the output as expected (value is 222) while the test passes with its assert value == 111.

In your example, @mock.patch("src.subsrc.util.foo") obviously worked, but probably didn't do what you intended. From your example code, I cannot see how @mock.patch("aaa.src.subsrc.util.foo") shouldn't have returned a ModuleNotFoundError.

Related