Search code examples
pythonunit-testingmockingpython-unittest

Patching an object by reference rather than by name string?


The most common way to patch something in a module seems to be to use something like

from unittest.mock import patch

from mypackage.my_module.my_submodule import function_to_test

@patch('mypackage.my_module.my_submodule.fits.open')
def test_something(self, mock_fits_open)
    # ...
    mock_fits_open.return_value = some_return_value
    function_to_test()
    # ...

However, with the value passed to the patch decorator being a string, I don't get lots of the nice benefits from IDE. I can't use parts of the string to jump to definitions. I don't get autocomplete (and an implicit spelling check). Nor full refactoring capabilities. And so on.

Using patch.object I can get much closer to what I'm looking for.

from unittest.mock import patch

import mypackage.my_module.my_submodule
from mypackage.my_module.my_submodule import function_to_test

@patch.object(mypackage.my_module.my_submodule.fits, 'open')
def test_something(self, mock_fits_open)
    # ...
    mock_fits_open.return_value = some_return_value
    function_to_test()
    # ...

However, this still requires the final part of the name of the referenced object is just a string. Is there a (nice) way to patch an object purely on the reference to that object? That is, I would like to be able to do something like

from unittest.mock import patch

import mypackage.my_module.my_submodule
from mypackage.my_module.my_submodule import function_to_test

@patch.reference(mypackage.my_module.my_submodule.fits.open)
def test_something(self, mock_fits_open)
    # ...
    mock_fits_open.return_value = some_return_value
    function_to_test()
    # ...

Solution

  • Patching works by replacing in the namespace where the name is looked up.

    The underlying logic of mock.patch is essentially working with a context-managed name shadowing. You could do the same thing manually with:

    • save original value associated with name (if any)
    • try overwriting the name
    • execute the code under test
    • finally resetting name back to the original value

    Therefore, you fundamentally need to patch on a name, there is no patching a reference directly.