Reputation: 35760
I have a package:
- package/
- __init__.py
- cache.py
- module1.py
- module2.py
- tests/
- test_module1.py
- test_module2.py
- conftest.py
both module1
and module2
are importing from cache.py
:
from package.cache import cache
@cache()
def foo():
...
by default, cache
is using file based caching provied by dogpile.cache, however when running tests, I'd like to mock cache
with a memory based caching which is also supported by dogpile.cache.
here is what I'm tring:
# conftest.py
import pytest
@pytest.fixture(autouse=True)
def patch_cache(monkeypatch):
from dogpile.cache import make_region
m_cache = make_region().configure('dogpile.cache.memory')
monkeypatch.setattr('package.cache.cache', m_cache)
as you can see, I created a fixture in which using monkeypatch
to replace cache
with m_cache
however, this doesn't work, when I run test with py.test
, they're still using the old file based cache. is there anything wrong?
Upvotes: 3
Views: 1299
Reputation: 7845
I had a similar problem recently with pytest and monkeypatching that I thought should have been resolved by using monkeypatch
. I was trying to swap out the cache in my flask application for an in-memory one so that my cached views and other things didn't clobber real application cache keys accidentally.
The problem I discovered is that, like unittest.mock.patch
, I had to monkeypatch the location where the patched thing is being imported and used, the call-site, in other words.
Imagine the following set of modules:
# package1/app1/module1.py
from flask_app import cache
cache.get("SOMEKEY")
# package1/app2/module1.py
from flask_app import cache
cache.get("SOMEKEY")
# package1/app2/module2.py
from package1.app2.module1 import cache
cache.get("SOMEKEY")
Now, in pytest in order to guarantee that all of these different versions of cache
have been monkeypatch
ed, I need a fixture that explicitly sets the attribute for all of them:
# conftest.py
from werkzeug.contrib.cache import SimpleCache
@pytest.fixture(scope="function", autouse=True)
def safe_cache(request, monkeypatch):
"""
Monkeypatch the cache so it doesn't clobber real keys.
Clear after every test.
"""
cache = SimpleCache()
monkeypatch.setattr('package1.app1.module1.cache', cache)
monkeypatch.setattr('package1.app2.module1.cache', cache)
monkeypatch.setattr('package1.app2.module2.cache', cache)
def teardown():
cache.clear()
request.addfinalizer(teardown)
return cache
This is kind of annoying because every time I write a new module that imports the cache, I must also monkeypatch it in the fixture that monkeypatches these caches.
It works, however.
When I set a breakpoint in one of the modules importing the cache, and check what it's using, I see the following:
ipdb> cache
<werkzeug.contrib.cache.SimpleCache object at 0x10d658ac8>
My flask application is using a Redis cache otherwise, so seeing the above shows me it has been successful.
Upvotes: 0
Reputation: 95626
@cache()
is applied when the module is imported, because the decorator is called at the top level of the module. If you monkeypatch it after that module is imported, your patched in version won't be applied.
Upvotes: 1