1

The patch function from the mock library is sensitive to how things are imported. Is there a deep reason why I can't just use the fully qualified name where the function was originally defined regardless of how it is imported in other modules?

using a "module import" works fine

patch_example.py:

# WORKS!
from mock import patch
import inner

def outer(x):
    return ("outer", inner.inner(x))

@patch("inner.inner")
def test(mock_inner):
    mock_inner.return_value = "MOCK"
    assert outer(1) == ("outer", "MOCK")
    return "SUCCESS"

if __name__ == "__main__":
    print test()

inner.py:

def inner(x):
    return ("inner.inner", x)

Running python patch_example.py just outputs success.

However, changing the import can have pretty dramatic consequences

Using a module alias still works

# WORKS!
from mock import patch
import inner as inner2

def outer(x):
    return ("outer", inner2.inner(x))

@patch("inner.inner")
def test(mock_inner):
    mock_inner.return_value = "MOCK"
    assert outer(1) == ("outer", "MOCK")
    return "SUCCESS"

if __name__ == "__main__":
    print test()

directly importing the symbol, however, requires you to change the fully qualified name.

direct import, inner.inner as fully qualified name.

# FAILS!
from mock import patch
from inner import inner

def outer(x):
    return ("outer", inner(x))

@patch("inner.inner")
def test(mock_inner):
    mock_inner.return_value = "MOCK"
    assert outer(1) == ("outer", "MOCK")
    return "SUCCESS"

if __name__ == "__main__":
    print test()

produces

% python patch_example.py
Traceback (most recent call last):
  File "patch_example.py", line 14, in <module>
    print test()
  File "/usr/local/lib/python2.7/site-packages/mock/mock.py", line 1305, in patched
    return func(*args, **keywargs)
  File "patch_example.py", line 10, in test
    assert outer(1) == ("outer", "MOCK")
AssertionError

If I update the fully qualified path to patch_example.inner, the patch still fails.

# FAILS!
from mock import patch
from inner import inner

def outer(x):
    return ("outer", inner(x))

@patch("patch_example.inner")
def test(mock_inner):
    mock_inner.return_value = "MOCK"
    assert outer(1) == ("outer", "MOCK")
    return "SUCCESS"

if __name__ == "__main__":
    print test()

% python patch_example.py

Traceback (most recent call last):
  File "patch_example.py", line 14, in <module>
    print test()
  File "/usr/local/lib/python2.7/site-packages/mock/mock.py", line 1305, in patched
    return func(*args, **keywargs)
  File "patch_example.py", line 10, in test
    assert outer(1) == ("outer", "MOCK")
AssertionError

using __main__.inner as my fully qualified name patches the right thing though.

# WORKS!
from mock import patch
from inner import inner

def outer(x):
    return ("outer", inner(x))

@patch("__main__.inner")
def test(mock_inner):
    mock_inner.return_value = "MOCK"
    assert outer(1) == ("outer", "MOCK")
    return "SUCCESS"

if __name__ == "__main__":
    print test()

prints "SUCCESS"

So, why can't I patch the value of inner when it's imported as from inner import inner using either the fully qualified name of the original symbol inner.inner or the use the name of the main python module rather than __name__?

Tested with Python 2.7.12 on OS X.

Greg Nisbet
  • 6,710
  • 3
  • 25
  • 65

1 Answers1

1

The problem is that once you directly import the symbol there is no link whatsoever between the binding you are using in the __main__ module and the binding found in the inner module. So patching the module does not change the already imported symbols.

Importing a module using an alias doesn't matter because patch will lookup the sys.modules dictionary, which still keep track of the original name, so that's why this works (actually: when calling the mock the module is freshly imported, so it doesn't matter the name in which you imported it when calling patch)

In other words: you have to patch both bindings because they are effectively unrelated. There is no way for patch to know where all references to inner.inner ended up and patch them.

In this situation the second argument of patch might be useful to specify an existing mock object that can be shared to patch all bindings.

Bakuriu
  • 98,325
  • 22
  • 197
  • 231