Search code examples
pythonpython-3.xpython-unittestpython-mock

What is the reason that mock.patch ignores a fully imported function?


Today I realized that it matters for unittest.mock.patch how I import a function. Depending on the used way a mock.patch call worked or was ignored. In Python we typically import a function with:

  • an import statement like import os or
  • a from ... import ... statement like from os import system

A mock.patch works like a charm if I use import os, but it was ignored if I patch a from os import system.

Example 1: Using import

import os
from unittest import mock


def echo():
    os.system('echo "Hello"')


with mock.patch('os.system') as mocked:
    print(mocked)
    mocked.side_effect = Exception('Patch works!')
    echo()

Output of example 1

<MagicMock name='system' id='140037358656760'>

Traceback (most recent call last):
  File "/.../config/scratches/scratch_7.py", line 12, in <module>
    echo()
  File "/.../config/scratches/scratch_7.py", line 6, in echo
    os.system('echo "Hello"')
  File "/.../python3.5/unittest/mock.py", line 917, in __call__
    return _mock_self._mock_call(*args, **kwargs)
  File "/.../python3.5/unittest/mock.py", line 973, in _mock_call
    raise effect
Exception: Patch works!

Example 2: Using a full function import and from-import

When I fully import os.system the mock.patch ignores the mocked.side_effect.

from os import system
from unittest import mock


def echo():
    system('echo "Hello"')


with mock.patch('os.system') as mocked:
    print(mocked)
    mocked.side_effect = Exception('Patching does not work!')
    echo()

    print('Patch was ignored!')

Output of example 2

<MagicMock name='system' id='139851175427376'>
Hello
Patch was ignored!

In both cases I don't receive an error and mock could find os.system as a valid path. However, in the second case the function is not properly patched.

  • Why mock.patch does not patch the function in the second example?
  • Are there any implementation specific reasons why the second patch did not work?

Solution

  • When you do from os import system, you get a variable named system pointing to os.system function. Later, you assign, via patching, a different function to os.system, but system keeps to point to the old function. This is the same reason why the following works:

    tmp = a
    a = b
    b = tmp
    

    It doesn't happen in the first example, because you reference os.system before it is mocked. To fix your second example, I'd go with the following:

    from os import system
    from unittest import mock
    
    def echo():
        system('echo "Hello"')
    
    with mock.patch('__main__.system') as mocked:
        print(mocked)
        mocked.side_effect = Exception('Patching does not work!')
        echo()
    
        print('Patch was ignored!')
    

    This way you make sure you patch the right reference. This is a rather common pattern. If echo function were in a file named echo.py, the patch call would look like with mock.patch('echo.system').