skip to Main Content

I am using redis in the following way:

from redis import Redis

redis_client = Redis()

def get_datetime_from_redis(key):

    start_time = redis_client.get(key)
    start_time = datetime.strptime(datetime_as_string, "%Y-%m-%d %H:%M:%S.%f")
    duration = (datetime.now() - start_time).total_seconds()
    return duration

I’d like to test the function in the following way:

import pytest
from core.utils import get_datetime_from_redis
from unittest.mock import Mock
from datetime import datetime


def test_get_datetime_from_redis(monkeypatch):
    
    mock_redis = Mock()
    mock_redis.get.return_value = datetime(2022, 1, 26)
    monkeypatch.setattr('core.utils.Redis', mock_redis)

    get_datetime_from_redis('foo')
    
    mock_redis.get.assert_called_once()
    

But the issue is that by the time that monkeypatch.setattr('core.utils.Redis', mock_redis) is run, redis_client is already instantiated, so I’m not mocking the correct version.

How do I address these kinds of tests?

2

Answers


  1. Not sure it will solve entirely the problem but you should design your tests to have a fixture in charge of managing the creation of the mocked redis (mock_redis). By encapsulating this code in a fixture you will be able to manage when the fixture is created (once per session, per module, per function) and to gather all this code at a single location.
    Here is a good example on how to do that.

    Second point is on the way you instantiate redis_client client in your code. Since it’s not done in a method or in a class, redis_client is created as soon as the module is imported. You should also consider deferring the instantiation by putting it in a method or class you can call when needed.

    Login or Signup to reply.
  2. The fact that it is a global variable should not be an issue : monckeypatch can be apply on every scope and even existing instances.

    The issue here is the way you are using the monckeypatch fixture.

    Since the function is consuming redis_clent variable from the core.utils module, the monckeypatch fixture should mock it, instead of the Redis class itself.

    So :

    import pytest
    from core.utils import redis_client
    from core.utils import get_datetime_from_redis
    from datetime import datetime
    
    
    def test_get_datetime_from_redis(monkeypatch):
        redis_get_called = []
    
        # define mock function
        def mock_redis_get(self, k, default=None) :
            get_called.append(1) # Add to call count
            return datetime(2022, 1, 26)
        
        # patch redis_client variable
        monkeypatch.setattr(redis_client, 'get', mock_redis_get)
    
        # act  
        get_datetime_from_redis('foo')
    
        # assert
        assert len(redis_get_called) == 1
    

    Now just pay attention to the signature of the Redisget method :

    In monckeypatch , it is a good idea to have the same signature than the mocked function… this is why the mock function here has also a self, k (and not key), and a default parameters (I think that this last one can be ignored since it is an optional parameter)

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search