Home > Blockchain >  How to mock the import of a module in python?
How to mock the import of a module in python?

Time:11-18

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'>
  • Related