Using django-cacheops, I want to test that my views are getting cached as I intend them to be. In my test case I'm connecting cacheops cache_read
signal to a handler that should increment a value in the cache for hits or misses. However, the signal is never fired. Does anyone know the correct way to connect a django signal handler in a testcase, purely for use in that testcase?
here's what I have so far
from cacheops.signals import cache_read
cache.set('test_cache_hits', 0)
cache.set('test_cache_misses', 0)
def cache_log(sender, func, hit, **kwargs):
# never called
if hit:
cache.incr('test_cache_hits')
else:
cache.incr('test_cache_misses')
class BootstrapTests(TestCase):
@classmethod
def setUpClass(cls):
super(BootstrapTests, cls).setUpClass()
cache_read.connect(cache_log)
assert cache_read.has_listeners()
def test_something_that_should_fill_and_retrieve_cache(self):
....
hits = cache.get('test_cache_hits') # always 0
I've also tried connecting the signal handler at the module level, and in the regular testcase setUp
method, all with the same result.
EDIT:
Here's my actual test code, plus the object I'm testing. I'm using the cached_as
decorator to cache a function. This test is currently failing.
boostrap.py
class BootstrapData(object):
def __init__(self, app, person=None):
self.app = app
def get_homepage_dict(self, context={}):
url_name = self.app.url_name
@cached_as(App.objects.filter(url_name=url_name), extra=context)
def _get_homepage_dict():
if self.app.homepage is None:
return None
concrete_module_class = MODULE_MAPPING[self.app.homepage.type]
serializer_class_name = f'{concrete_module_class.__name__}Serializer'
serializer_class = getattr(api.serializers, serializer_class_name)
concrete_module = concrete_module_class.objects.get(module=self.app.homepage)
serializer = serializer_class(context=context)
key = concrete_module_class.__name__
return {
key: serializer.to_representation(instance=concrete_module)
}
return _get_homepage_dict()
test_bootstrap.py
class BootstrapDataTest(TestCase):
def setUp(self):
super(BootstrapDataTest, self).setUp()
def set_signal(signal=None, **kwargs):
self.signal_calls.append(kwargs)
self.signal_calls = []
cache_read.connect(set_signal, dispatch_uid=1, weak=False)
self.app = self.setup_basic_app() # creates an 'App' model and saves it
def tearDown(self):
cache_read.disconnect(dispatch_uid=1)
def test_boostrap_data_is_cached(self):
obj = BootstrapData(self.app)
obj.get_homepage_dict()
# fails, self.signal_calls == []
self.assertEqual(self.signal_calls, [{'sender': App, 'func': None, 'hit': False }])
self.signal_calls = []
obj.get_homepage_dict()
self.assertEqual(self.signal_calls, [{'sender': App, 'func': None, 'hit': True}])
First, to dispel a misconception about signals, they are not executed asynchronously. There is no background thread or worker to execute them. Like most of Django, they are fully "synchronous".
Post-save SignalThe post_save logic is just a normal function, the receiver function, but it's connected to a sender, which is the Order model. The code block below demonstrates the sample receiver function as a post-save. 1from django. db. models.
To notify another part of the application after the delete event of an object happens, you can use the post_delete signal.
I can't see why this is happening but I will try to make a useful answer anyway.
First, if you want to test whether cache works you shouldn't rely on its own side effects to check that, and signals are side effects of its primary function - preventing db calls. Try testing that:
def test_it_works(self):
with self.assertNumQueries(1):
obj.get_homepage_dict()
with self.assertNumQueries(0):
obj.get_homepage_dict()
Second, if you want to know what's going on you may dig in adding prints everywhere including cacheops code and see where it stops. Alternatively, you can make a test for me to see, the instruction is here https://github.com/Suor/django-cacheops#writing-a-test.
Last, your test is a bit wrong. For @cached_as()
sender would be None
and func would be decorated function.
In this specific case, it turned out to be that my test cases subclassed django rest framework's APITestCase, which in turn subclasses django's SimpleTestCase.
looking in the cacheops sources, I found that those tests subclass TransactionTestCase, and switching out the test case fixed this issue.
Would be interested to know why this is the case but the issue is solved for now.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With