45

In order to avoid a circular import, I've been forced to define a function that looks like:

# do_something.py

def do_it():
    from .helpers import do_it_helper
    # do stuff

Now I'd like to be able to test this function, with do_it_helper patched over. If the import were a top level import,

class Test_do_it(unittest.TestCase):
    def test_do_it(self):
        with patch('do_something.do_it_helper') as helper_mock:
            helper_mock.return_value = 12
            # test things

would work fine. However, the code above gives me:

AttributeError: <module 'do_something'> does not have the attribute 'do_it_helper'

On a whim, I also tried changing the patch statement to:

with patch('do_something.do_it.do_it_helper') as helper_mock:

But that produced a similar error. Is there any way to mock this function, given the fact that I'm forced into importing it within the function where it's used?

alecxe
  • 462,703
  • 120
  • 1,088
  • 1,195
Wilduck
  • 13,822
  • 10
  • 58
  • 90

1 Answers1

61

You should mock out helpers.do_it_helper:

class Test_do_it(unittest.TestCase):
    def test_do_it(self):
        with patch('helpers.do_it_helper') as helper_mock:
            helper_mock.return_value = 12
            # test things

Here's an example using mock on os.getcwd():

import unittest
from mock import patch


def get_cwd():
    from os import getcwd
    return getcwd()


class MyTestCase(unittest.TestCase):
    @patch('os.getcwd')
    def test_mocked(self, mock_function):
        mock_function.return_value = 'test'
        self.assertEqual(get_cwd(), 'test')
starball
  • 20,030
  • 7
  • 43
  • 238
alecxe
  • 462,703
  • 120
  • 1,088
  • 1,195
  • 3
    That totally works! Where to patch has always been confusing for me. The rule of thumb from the docs is "patch where an object is looked up, which is not necessarily the same place as where it is defined." In this case, though, you patch where it is defined. Why is that? – Wilduck Mar 05 '14 at 15:26
  • @Wilduck well, pay attention to that `necessarily` word - sounds a bit "two-edged" :) – alecxe Mar 05 '14 at 15:34
  • 6
    @alecxe sorry for bumping this question again after so many years. I basically have the same question as OP that why the object is looked up in its original file not in the new file? I tried to google it, but every one just touched it lightly - claim it is binded to the local scope and that's all. – Flowing Cloud Sep 08 '18 at 02:03
  • 1
    In Python, when you import something, it becomes part of the module's scope. Python's docs explains it: "The import statement combines two operations; it searches for the named module, then it binds the results of that search to a name in the local scope." (https://docs.python.org/3/reference/import.html) – syastrov Oct 15 '18 at 14:50
  • 7
    This behavior is still clear as mud to me. I'm happy for the resolution to this problem, but I lament my continued confusion around it. – Rico Jan 23 '20 at 17:48
  • 1
    @Rico Late answer, but think about when the import statement executes. At the module level, it executes when your file is interpreted before any functions run, so the import creates a reference to the underlying imported function before your test even sets up. If you mock the original function at that point, you replace its symbol, but the module under test already has a handle on the underlying code. However when the import is in a function, it executes when the function is called, which means mocking the original function happens before the test executes and you are okay. – sirdodger May 09 '23 at 00:06