jazzband / django-waffle

A feature flipper for Django
https://waffle.readthedocs.io
BSD 3-Clause "New" or "Revised" License
1.12k stars 258 forks source link

Unit testing fails with FakeRedis during caching on custom model #497

Closed jlleblanc closed 7 months ago

jlleblanc commented 8 months ago

Hello there, I have created a custom model for my feature flag with Django Waffle 4.1.0. I am needing to flag features by company, which is coincidentally the same example as the one in the custom model documentation. So I am running that code.

Everything seems to be working: I am able to turn flags on and off, limit them by company, limit them by user, and also force them on or off for everyone. I am ultimately delivering flags though an endpoint to the client and this is working.

However, the tests are not working. I first attempted to create my own factory for feature flags, then called that factory in my tests:

FlagFactory(name="myFlagNameHere")

But when I try to run the test, it fails on that line. As I go through the stack trace, the last line within Waffle is this one:

/usr/local/lib/python3.9/site-packages/waffle/managers.py:24: in create
    cache.delete(cache_key)

The error is ultimately occurs here:

E           redis.exceptions.DataError: Invalid input of type: 'RedisCache'. Convert to a bytes, string, int or float first.

/usr/local/lib/python3.9/site-packages/redis/connection.py:119: DataError

We are using fakeredis version 1.4.1 in our tests elsewhere without a problem (although we will be upgrading that eventually).

I tried using the override_flag helper from waffle.testutils, but that also failed in the flush function of Waffle:

_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
/usr/local/lib/python3.9/site-packages/django/test/utils.py:351: in __enter__
    return self.enable()
/usr/local/lib/python3.9/site-packages/waffle/testutils.py:37: in enable
    self.update(self.active)
/usr/local/lib/python3.9/site-packages/waffle/testutils.py:88: in update
    obj.flush()
/usr/local/lib/python3.9/site-packages/waffle/models.py:222: in flush
    cache.delete_many(keys)
/usr/local/lib/python3.9/site-packages/django_redis/cache.py:32: in _decorator
    return method(self, *args, **kwargs)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <django_redis.cache.RedisCache object at 0xffff7b693430>
args = (['waffle:4.1.03c72927223809b54b6563a7624fbdf1e', 'flags:all', 'waffle:4.1.04f6cfa30ab69c1d16771bec8ff782556', 'waffle:4.1.0f6eac2bca1de6476a7a461c8b8e870c3', 'waffle:4.1.06d385ccda9014d31ae234ff3ea05a96b'],)
kwargs = {}

    @omit_exception
    def delete_many(self, *args, **kwargs):
>       return self.client.delete_many(*args, **kwargs)
E       AttributeError: 'FakeRedis' object has no attribute 'delete_many'

/usr/local/lib/python3.9/site-packages/django_redis/cache.py:100: AttributeError

I searched the FakeRedis repository just now for delete_many and nothing turns up for any version in the history of the project.

Assuming everything else is okay, is there a way for me to override flush just during testing to be no-op?

jlleblanc commented 7 months ago

Update: we have since upgraded to the latest version of fakeredis: 2.20.1. But still encountering this issue during unit testing.

jlleblanc commented 7 months ago

Another update: rather than try to get to the bottom of the Redis issues, I am looking at skipping the cache while in test mode. I am using WAFFLE_READ_FROM_WRITE_DB = True in settings.py, but this does not have any effect.

I am beginning to think my custom Flag model is either missing a function, or has a function that is written incorrectly. Has anyone else run into anything like this?

jlleblanc commented 7 months ago

Resolution: I am patching django_redis.cache.RedisCache.delete_many:

from unittest.mock import patch

# ..
# ..

    @patch("django_redis.cache.RedisCache.delete_many")
    def test_name_goes_here(self, mock_redis_delete_many):
        mock_redis_delete_many.return_value = None  # Mock the delete_many call to return None
        with override_flag("myFeatureFlag", active=True):
          # test itself went here