How to mock a function, that is imported within an imported method from different module

Igle picture Igle · Oct 12, 2017 · Viewed 7.7k times · Source

I got the following function to test:

my_package.db_engine.db_functions.py:

from ..utils import execute_cmd
from my_package.db_engine.db_functions import dbinfo 

def dbinfo(db_name):
    params = (cmd_cfg.DB, add_pj_suffix(db_name))
    cmd = get_db_cmd_string(cmd_cfg.DBINFO, params=params)
    cmd_result = execute_cmd(cmd)
    result_dict = map_cmd_output_to_dict(cmd_result)
    return result_dict

This function takes the name of a database, then builds a command string from it and executes this command as subprocess with the execute_cmd method. I want to test this function without actually executing the subprocess. I only want to check if the command is built correctly and correctly passed to execute_cmd. Therefore I need to mock the execute_cmd method which is imported from module utils.

My folder structure is the following:

my_project
|_src
| |_my_package
| | |_db_engine
| | | |_db_functions.py
| | | |_ __init__.py
| | |_utils.py
| | |_ __init__.py
| | |_ ....
| |_ __init__.py
|_tests
  |_test_db_engine.py

So for my test I tried the following in test_db_engine.py:

import unittest
from mock import patch

from my_pacakge.db_engine.db_functions import dbinfo


def execute_db_info_cmd_mock():
    return {
            'Last Checkpoint': '1.7',
            'Last Checkpoint Date': 'May 20, 2015 10:07:41 AM'
    }


class DBEngineTestSuite(unittest.TestCase):
    """ Tests für DB Engine"""

    @patch('my_package.utils.execute_cmd')
    def test_dbinfo(self, test_patch):
        test_patch.return_value = execute_db_info_cmd_mock()
        db_info = dbinfo('MyDBNameHere')
        self.assertEqual(sandbox_info['Last Checkpoint'], '1.7')

The execution of the actual command yields 1.6 for Last Checkpoint. So to verify if the mock return value is used, I set it to 1.7. But the mock for the function is not used, as the execution of the test case still yields 1.6 because it is executing the actual function that should have been patched with the mock.

Any idea what I got wrong here?

Answer

Martijn Pieters picture Martijn Pieters · Oct 12, 2017

You are patching the wrong location. From the Where to patch section:

patch() works by (temporarily) changing the object that a name points to with another one. There can be many names pointing to any individual object, so for patching to work you must ensure that you patch the name used by the system under test.

The basic principle is that you patch where an object is looked up, which is not necessarily the same place as where it is defined.

Your code-under-test finds execute_cmd as a global in their own module, but you didn't patch that reference:

from ..utils import execute_cmd

The my_package.utils.execute_cmd reference is patched, but that execute_cmd reference in my_package.db_engine.db_functions will still point to the original, unpatched function.

Patch the imported global instead:

@patch('my_package.db_engine.db_functions.execute_cmd')

Now the execute_cmd lookup inside dbinfo will use the patched mock object rather than the original global bound by the from ... import ... statement.