Search code examples
pythonunit-testingmockingpython-unittestpython-mock

How can I mock any function which is not being called directly?


TL;DR

How can I patch or mock "any functions that are not being called/used directly"?

Sceneario

I have a simple unit-test snippet as

# utils/functions.py
def get_user_agents():
    # sends requests to a private network and pulls data
    return pulled_data


# my_module/tasks.py
def create_foo():
    from utils.functions import get_user_agents
    value = get_user_agents()
    # do something with value
    return some_value


# test.py
class TestFooKlass(unittest.TestCase):
    def setUp(self):
        create_foo()

    def test_foo(self):
        ...

Here in setUp() method I am calling get_user_agents() function indirectly by calling create_foo(). During this execution I have got socket.timeout exception since get_user_agents() tried to access a private network.

So, how can I manipulate the return data or the entire get_user_agents function during the test?

Also, is there any way to persists this mock/patch during the whole test suite execution?


Solution

  • It does not matter that you call the function indirectly - important is to patch it as it is imported. In your example you import the function to be patched locally inside the tested function, so it will only be imported at function run time. In this case you have to patch the function as imported from its module (e.g. 'utils.functions.get_user_agents'):

    class TestFooKlass(unittest.TestCase):
        def setUp(self):
            self.patcher = mock.patch('utils.functions.get_user_agents',
                                      return_value='foo')  # whatever it shall return in the test 
            self.patcher.start()  # this returns the patched object, i  case you need it
            create_foo()
    
        def tearDown(self):
            self.patcher.stop()
    
        def test_foo(self):
            ...
    

    If you had imported the function instead at module level like:

    from utils.functions import get_user_agents
    
    def create_foo():
        value = get_user_agents()
        ...
    

    you should have patched the imported instance instead:

            self.patcher = mock.patch('my_module.tasks.get_user_agents',
                                      return_value='foo')
    

    As for patching the module for all tests: you can start patching in setUp as shown above, and stop it in tearDown.