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
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 thefixture
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.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 thecore.utils
module, themonckeypatch
fixture should mock it, instead of the Redis class itself.So :
Now just pay attention to the signature of the Redis
get
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 notkey
), and adefault
parameters (I think that this last one can be ignored since it is an optional parameter)