When the source code was initially read, it already stored the original submethod_b function within the variable _method_registry, thus by the time you patched it at runtime, although the function was replaced, it didn't change the already-stored value in this variable. To prove it, let's add prints.
main.py
def submethod_a():
pass
def submethod_b():
pass
_method_registry = {
"a": submethod_a,
"b": submethod_b,
}
def main_method(key):
print(_method_registry["b"], "- Stored function")
print(submethod_b, "- Patched function")
test_main.py
from unittest.mock import patch
from main import main_method
def test_no_patch():
main_method("b")
@patch("main.submethod_b")
def test_with_patch(mocked_submethod_b):
main_method("b")
Output
$ pytest -q -rP
================================================================================================= PASSES ==================================================================================================
______________________________________________________________________________________________ test_no_patch ______________________________________________________________________________________________
------------------------------------------------------------------------------------------ Captured stdout call -------------------------------------------------------------------------------------------
<function submethod_b at 0x7fba0fb63ee0> - Stored function
<function submethod_b at 0x7fba0fb63ee0> - Patched function
_____________________________________________________________________________________________ test_with_patch _____________________________________________________________________________________________
------------------------------------------------------------------------------------------ Captured stdout call -------------------------------------------------------------------------------------------
<function submethod_b at 0x7fba0fb63ee0> - Stored function
<MagicMock name='submethod_b' id='140437103934432'> - Patched function
2 passed in 0.04s
As you can see, without the patch, the value is just the same original function. But with the patch, notice that only the actual function was replaced, not the one already stored.
Alternative Solution
- Put the submethod functions to its own file.
- Import the submethods to your main file and use as originally done.
- Patch the submethod file.
- Reload the main file so that the patched submethods would be the one used when recreating the
_method_registry.
main.py
from submethod import submethod_a, submethod_b # Or: import submethod
_method_registry = {
"a": submethod_a, # Depending on chosen import style, this can be: submethod.submethod_a
"b": submethod_b, # Depending on chosen import style, this can be: submethod.submethod_b
}
def main_method(key):
print(_method_registry["b"], "- Stored function")
print(submethod_b, "- Patched function")
method_to_use = _method_registry[key]
method_to_use()
submethod.py
def submethod_a():
pass
def submethod_b():
pass
test_main.py
from importlib import reload
import sys
from unittest.mock import patch
from main import main_method
@patch("submethod.submethod_b")
def test_with_patch(mocked_submethod_b):
# Now that the patch is in effect and replaced the submethod.submethod_b, reload the main file so that it recreates _method_registry with the patched function
reload(sys.modules['main'])
main_method("b")
assert mocked_submethod_b.called
Output
$ pytest -q -rP
================================================================================================= PASSES ==================================================================================================
_____________________________________________________________________________________________ test_with_patch _____________________________________________________________________________________________
------------------------------------------------------------------------------------------ Captured stdout call -------------------------------------------------------------------------------------------
<MagicMock name='submethod_b' id='140432453077360'> - Stored function
<MagicMock name='submethod_b' id='140432453077360'> - Patched function
1 passed in 0.04s
Now, the stored function and the patched function itself both points to the mocked version. Thus, the .called assertion is now successful.