ItayB
ItayB

Reputation: 11337

disabling functools.cache for tests

I'm using the @cache decorator in my methods:

class Configuration:

    @classmethod
    @property
    @cache
    def whitelist_sections(cls) -> Set[int]:
        whitelist_sections: Set[int] = set()
        whitelist: str = os.getenv("WHITELIST_SECTIONS", "")
        try:
            whitelist_sections = eval("{" + whitelist + "}")
        except ValueError:
            return whitelist_sections
        return whitelist_sections

I want to test the method above, here are my efforts:

def test_whitelist_sections():
    section_id = 8765432
    env_var_mock = mock.patch.dict(os.environ, {"WHITELIST_SECTIONS": str(section_id)})
    env_var_mock.start()
    whitelist_sections = Configuration.whitelist_sections
    env_var_mock.stop()
    assert whitelist_sections == {section_id}


def test_whitelist_multiple_sections():
    section_id_1 = 8765432
    section_id_2 = 1234567
    env_var_mock = mock.patch.dict(os.environ, {"WHITELIST_SECTIONS": f"{section_id_1},{section_id_2}"})
    env_var_mock.start()
    whitelist_sections = Configuration.whitelist_sections
    env_var_mock.stop()
    assert whitelist_sections == {section_id_1, section_id_2}


def test_whitelist_sections_empty():
    env_var_mock = mock.patch.dict(os.environ, {"WHITELIST_SECTIONS": ""})
    env_var_mock.start()
    whitelist_sections = Configuration.whitelist_sections
    env_var_mock.stop()
    assert whitelist_sections == {}

The problem is the cache which doesn't allow me to test different scenarios. How can I mock the @cache decorator?

I tried this approach but it didn't work:

cache_mock = mock.patch("functools.cache", lambda func: func)

EDIT: tried this approach:

def mock_cache(function):
    return function()


def test_whitelist_sections():
    section_id = 8765432
    env_var_mock = mock.patch.dict(os.environ, {"WHITELIST_SECTIONS": str(section_id)})
    env_var_mock.start()
    cache_mock = mock.patch("my_module.configuration.cache", mock_cache)
    cache_mock.start()
    whitelist_sections = Configuration.whitelist_sections
    cache_mock.stop()
    env_var_mock.stop()
    assert whitelist_sections == {section_id}

Running in debug mode I can see that the decorator was replaced by mock_cache but running all together with pytest still yield the same error for two (out of free) tests - only first one work

EDIT2: note that that's a static method (@classmethod)

Upvotes: 3

Views: 2716

Answers (2)

Logi
Logi

Reputation: 111

The @cache decorator adds a cache_clear attribute to the function that it decorates, so the simplest approach is to add this line to your test before you call the cached function:

whitelist_sections.cache_clear()

If you find yourself doing this in a lot of your tests, you can make an auto-use fixture which always clears this cache.

@pytest.fixture(autouse=True)
def clear_caches():
    whitelist_sections.cache_clear()
    other_cached_function.cache_clear()

Add it either in your test file or a suitably located conftest.py file to affect all tests recursively in that module.

Upvotes: 9

Ghasem
Ghasem

Reputation: 15573

For the ones using cachetools package, you need to use the cache_clear method. For example if your function is something like this:

from cachetools.func import ttl_cache

@ttl_cache(maxsize=100, ttl=360)
def foo(x):
    # Do somthing with x

Then you can create a fixture with pytest like this:

@pytest.fixture(autouse=True)
def clear_cache():
    yield
    foo.cache_clear()

Upvotes: 1

Related Questions