I have a function that has a decorator. The decorator accepts arguments and the value of the argument is derived from another function call.
example.py
from cachetools import cached
from cachetools import TTLCache
from other import get_value
@cached(cache=TTLCache(maxsize=1, ttl=get_value('cache_ttl')))
def my_func():
return 'result'
other.py
def get_value(key):
data = {
'cache_ttl': 10,
}
# Let's assume here we launch a shuttle to the space too.
return data[key]
I'd like to mock the call to get_value()
. I'm using the following in my test:
example_test.py
import mock
import pytest
from example import my_func
@pytest.fixture
def mock_get_value():
with mock.patch(
"example.get_value",
autospec=True,
) as _mock:
yield _mock
def test_my_func(mock_get_value):
assert my_func() == 'result'
Here I'm injecting mock_get_value
to test_my_func. However, since my decorator is called on the first import, get_value()
gets called immediately. Any idea if there's a way to mock the call to get_value()
before module is imported right away using pytest?
Move the from example import my_func
inside your with
in your test function. Also patch it where it's really coming from, other.get_value
. That may be all it takes.
Python caches modules in sys.modules
, so module-level code (like function definitions) only runs on the first import from anywhere. If this isn't the first time, you can force a re-import using either importlib.reload()
or by deleting the appropriate key in sys.modules
and importing again.
Beware that re-importing a module may have side effects, and you may also want to re-import the module again after running the test to avoid interfering with other tests. If another module was using objects defined in the re-imported module, these don't just disappear, and may not be updated the way it expects. For example, re-importing a module may create a second instance of what was supposed to be a singleton.
One more robust approach would be save the original imported module object somewhere else, delete from sys.modules
, re-import with the patched version for the duration of the test, and then put back the original import into sys.modules
after the test. You could do this with an import inside of a patch.dict()
context on sys.modules
.
import mock
import sys
import pytest
@pytest.fixture
def mock_get_value():
with mock.patch(
"other.get_value",
autospec=True,
) as _mock, mock.patch.dict("sys.modules"):
sys.modules.pop("example", None)
yield _mock
def test_my_func(mock_get_value):
from example import my_func
assert my_func() == 'result'
Another possibility is to call the decorator yourself in the test, on the original function. If the decorator used functools.wraps()
/functools.update_wrapper()
, then the original function should be available as a __wrapped__
attribute. This may not be available depending on how the decorator was implemented.