Consider the following project structure:
/project
- folder/
- some_config_module.py
- func.py
- test/
- __init__.py
- test_config.py
- test.py
Assume the following code
# func.py
import folder.some_config_module as config
def search_for_data():
var = config.predefined_parameter
# rest of code
I would now like to write a unit-test for search_for_data
. To do this in a sensible way, I need to mock the import of the some_config_module
, i.e. my try
# test.py
import unittest
from unittest.mock import patch
import tests.test_config as test_config
from folder.func import serach_for_data
class TestSearchForData(unittest.TestCase):
@patch('folder.some_config_module')
def test_one(self, test_config):
self.assertEqual(search_for_data(), 0)
if __name__ == "__main__":
unittest.main()
This does not result in the expected behaviour: what I would like is for search_for_data
inside of test_one
to import test.test_config
and not folder.some_config_module
. This code was based on this answer... But I seem to misunderstand something rather fundamental about mock
..
CodePudding user response:
Since your function search_for_data
is using the name config
imported in func
's global namespace, you need to override that instead of the overriding folder.some_config_module
.
@patch('folder.func.config')
An import like import folder.some_config_module as config
is equivalent to doing:
import folder.some_config_module
config = folder.some_config_module
del folder.some_config_module
Hence it adds the variable config
to that module(func.py
)'s namespace and that's what then search_for_data
is using. From search_for_data
's perspective, it doesn't matter if config
is a name that refers to a module or it refers to something else, it just knows that it needs to load that name from the global namespace(in this case it's the module func.py
)
At runtime search_for_data
will look for config
in its global namespace(search_for_data.__globals__
), mock patches this globals dictionary and replaces the name config
in this dictionary temporarily with the mock object and during teardown it restores it back.
import os.path as path
from unittest.mock import patch
def func():
print(path)
print(f"Before patch: {func.__globals__['path']}")
with patch("__main__.path"):
print(f"During patch: {func.__globals__['path']}")
func()
print(f"After patch: {func.__globals__['path']}")
Outputs:
Before patch: <module 'posixpath' from '/usr/lib/python3.8/posixpath.py'>
During patch: <MagicMock name='path' id='140233829017776'>
<MagicMock name='path' id='140233829017776'>
After patch: <module 'posixpath' from '/usr/lib/python3.8/posixpath.py'>