7

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()
    # ...
golmschenk
  • 11,736
  • 20
  • 78
  • 137
  • 1
    No, because ultimately objects aren't patched; names *referring* to objects are patched. `patch.object` is, in some sense, analogous to `getattr`. The first argument can be an arbitrary expression, but you still need the name of one of its attributes to actually patch. – chepner Feb 13 '20 at 18:55
  • This is essentially the same question as *[Can an object inspect the name of the variable it's been assigned to?](https://stackoverflow.com/q/8875202/674039)* and the answer is basically no, names and objects live in different dimensions. – wim Feb 13 '20 at 18:55

2 Answers2

3

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.

wim
  • 338,267
  • 99
  • 616
  • 750
  • This seems to make sense. However, in my test function, I can do `tmp_open = mypackage.my_module.my_submodule.fits.open; mypackage.my_module.my_submodule.fits.open = Mock(); mypackage.my_module.my_submodule.fits.open.return_value = some_return_value; # Run test here; mypackage.my_module.my_submodule.fits.open = tmp_open`. And this accomplishes a similar setup and tear down using the reference alone. I'm guessing there's a reason why this wouldn't work in a more general decorator case? – golmschenk Feb 13 '20 at 20:19
  • 1
    It works because you used the name "open" by hardcoding it in your source code. `mock.patch` will do more or less the same thing, but to offer that monkeypatch in a general way it needs to know how to resolve the name. Does that make sense? – wim Feb 13 '20 at 20:38
1

You can use the __name__ attribute of the function object:

from unittest.mock import patch
import my_module

@patch.object(my_module, my_module.my_fun.__name__)
def test_something(self, mocked_fun)
    # ...

See also: Name of a Python function in a stack trace