Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Pytest unit test fails because target function has cachetools.ttl_cache decorator

I have a function that I am writing unit tests for using pytest. The only problem is that since I am writing multiple tests for the same function, a couple of tests fail because of the cachetools.ttl_cache decorator. This decorator makes the function return the same value every time it is being run which messes up the tests. This decorator is not present in the function that I am testing but in a function that is called by the one that I am testing. I cannot remove this decorator from the function I am testing. Here is the test:

@patch('load_balancer.model_helpers.DBSession')
def test_returns_true_if_split_test_is_external(self, dbsession, group_ctx):
    group_ctx.group_id = '{}-{}'.format('2222222222', '123456789')
    split_test = Mock()
    split_test.state = 'external'
    config = {
        'query.return_value.filter.return_value.first.return_value': split_test
    }
    dbsession.configure_mock(**config)
    assert group_ctx.is_in_variation_group('foo') == True

And here is the function to be tested:

def is_in_variation_group(self, split_test=None):
    try:
        split_test = get_split_test(split_test) # This function has the 
        #decorator
        log.info('Split test {} is set to {}'.format(split_test.name,
                                                     split_test.state))
        if not split_test or split_test.state == 'off':
            return False

        phone_number = int(self.group_id.split('-')[0])
        if split_test.state == 'internal':
            return True if str(phone_number) in INTERNAL_GUINEA_PIGS else False
        if split_test.state == 'external':
            return True if phone_number % 2 == 0 else False
    except Exception as e:
        log.warning("A {} occurred while evaluating membership into {}'s variation "
                    "group".format(e.__class__.__name__, split_test))

Get split test function:

    @cachetools.ttl_cache(maxsize=1024, ttl=60)
    def get_split_test(name):
         return (DBSession.query(SplitTest)
                 .filter(SplitTest.name == name)
                 .first())

How can I make it so this cache decorator is ignored? Any help is appreciated greatly

like image 538
Amrit Baveja Avatar asked Aug 02 '17 10:08

Amrit Baveja


2 Answers

I suggest clearing the function's cache after each test run.

The cachetools documentation doesn't mention this, but from the source code it appears the cache decorators expose a cache_clear function.

For your example code under test:

import cachetools.func

@cachetools.func.ttl_cache(maxsize=1024, ttl=60)
def get_split_test(name):
     return (DBSession.query(SplitTest)
             .filter(SplitTest.name == name)
             .first())

This would be my approach (assumes pytest >= 3, otherwise use the yield_fixture decorator):

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

def test_foo():
    pass # Test your function like normal.

That clear_cache fixture uses a yield fixture that is automatically used after each test (autouse=True) to perform the cleanup after each test. You also can use the request fixture and request.addfinalizer to run a cleanup function.

like image 57
Frank T Avatar answered Oct 24 '22 07:10

Frank T


I would assign the cache into a variable and then at the end of the test reset the cache

GET_USERS_CACHE = TTLCache(maxsize=128, ttl=60)

@cached(cache=GET_USERS_CACHE)
def get_users():
    pass

test file

def test_get_users():
    # test code here
    GET_USERS_CACHE.clear()
like image 1
Israel kusayev Avatar answered Oct 24 '22 07:10

Israel kusayev