Mocking Redis in Python's unittest
Tags: redis, mock, python, unittest
Published: 2022-01-06 17:50:16

---
title: Mocking Redis in Python's unittest
published: True
description: 
tags: redis, mock, python, unittest
//cover_image: https://direct_url_to_image.jpg
---
Hello folks,

This blog will guide you to mock redis without using any new library. Redis is used as cache in almost every application. It's very likely you will be required to mock redis at some point of writing those testcases. Scanning through solutions available on internet, I felt the need of way to *mock redis* should be documented in a blog.

So going by the saying 😂,
> fake it before you make it
> mock it before you rock it

Let's start 💁

##Back to Basics ⚡️
In this section let us have a refresher course on `patch`, `mock`, `side_effect` and `return_value`.

###1. Mock ###

A typical piece of code consists of is objects, function calls and variables. While writing tests we don't want to actual objects/methods/class-methods to execute, so we can replace it with a mock object or mock call.

###2. Patch ###
We now know that we can mock objects and functional calls but how we can establish for which function we have to associate a particular mock with?
Here patch comes into play, patch is a decorator which accepts fully qualified name of method to be mocked as string.

```python
@patch("app.function")
def test_01_another_function(self, mock_function):
    pass
```
Here we have patched function method which will automatically send a positional argument to the function you're decorating. Usually this position argument is an instance of `MagicMock`.

###3. Return Value ###
While writing tests we require mock method or mock class method to return a particular value. We can add the expected return value in **return_value** attribute of MagicMock instance.

Suppose we have a RandomObject class where we have to mock method `function`.
```python
from unittest.mock import patch, MagicMock
from app.module import RandomObject

@patch("app.module.RandomObject")
def test_01_another_function(self, mock_random):
    mock_random.function.return_value = "test-value"
    random_object = RandomObject()
    self.assertEqual(random_object.function(), "test-value")
```

###4. Side Effect ###
This is typically used to test if Exceptions are handled correctly in the code. When the patched function is called, the exception mentioned in **side_effect** is raised.

```python
from unittest.mock import patch, MagicMock
from app.module import RandomObject

@patch("app.module.RandomObject")
def test_01_another_function(self, mock_random):
    mock_random.function.side_effect = Exception("test-message")
    random_object = RandomObject()
    self.assertRaises(random_object.function(), Exception)
```


##Leveraging side_effect ☄️

Another way to use side_effect is we can pass a list of possible values which we want to bind with side effect attribute. Each time the patched function is called the mock will return next element in the list of values. Also we can have any set of data type (not specifically Exceptions).

```python
from unittest.mock import patch, MagicMock

mock = MagicMock()
side_effect_list = ["dummy_val", {"dummy":"value"}]  # list of values on which we want to be returned.
mock.side_effect = side_effect_list

mock("test")
>>> 'dummy_val'
mock("test")
>>> {'dummy': 'value'}
```
To leverage side_effect even further, we can even bind side_effect attribute with a method.

```python
from unittest.mock import patch, MagicMock

foo_dict = {"foo": "bar", "bar": "foo"}
def foo_function(key):  # function which we need to bind with side effect
    return foo_dict[key]

mock = MagicMock()
mock.side_effect = foo_function
mock("foo")
>>> 'bar'
mock("bar")
>>> 'foo'

```

##Mocking Redis 🔘
Now let's discuss how we can now use above to mock redis. Let us for now consider 5 most common redis methods:
- `get`
- `set`
- `hset`
- `hget`
- `exists`

Since redis is a key-value data store, we can use dictionary for caching these key-value pairs. We can then define above methods in a MockRedis class.

```python
class MockRedis:
    def __init__(self, cache=dict()):
        self.cache = cache
```
Now let us write function that will mimic the [`get`](https://redis.io/commands/GET) functionality. The get method will simply take a key and return its value.

```python
    def get(self, key):
        if key in self.cache:
            return self.cache[key]
        return None  # return nil
```

[`set`](https://redis.io/commands/set) functionality puts the value in the cache.
```python
    def set(self, key, value, *args, **kwargs):
        if self.cache:
           self.cache[key] = value
           return "OK"
        return None  # return nil in case of some issue
```
Similarly let us implement [`hset`](https://redis.io/commands/hset), [`hget`](https://redis.io/commands/hget) and [`exists`](https://redis.io/commands/exists) in the class MockRedis.

```python
class MockRedis:
    def __init__(self, cache=dict()):
        self.cache = cache

    def get(self, key):
        if key in self.cache:
            return self.cache[key]
        return None  # return nil

    def set(self, key, value, *args, **kwargs):
        if self.cache:
           self.cache[key] = value
           return "OK"
        return None  # return nil in case of some issue

    def hget(self, hash, key):
        if hash in self.cache:
            if key in self.cache[hash]:
                return self.cache[hash][key]
        return None  # return nil

    def hset(self, hash, key, value, *args, **kwargs):
        if self.cache:
           self.cache[hash][key] = value
           return 1
        return None  # return nil in case of some issue

    def exists(self, key):
        if key in self.cache:
            return 1
        return 0

    def cache_overwrite(self, cache=dict()):
        self.cache = cache
```
*mock_redis_method.py*

So now let us mock redis now, for that we have to patch StrictRedis.

```python
from mock_redis_method import MockRedis
from unittest.mock import patch, MagicMock

@patch("redis.StrictRedis")
def test_01_redis(self, mock_redis):
    # initialising the cache with test values 
    redis_cache = {
        "foo": "bar", 
        "foobar": {"Foo": "Bar"}
    }
    
    mock_redis_obj = MockRedis(redis_cache)

    # binding a side_effect of a MagicMock instance with redis methods we defined in the MockRedis class.
    mock_redis_method = MagicMock()
    mock_redis_method.hget = Mock(side_effect=mock_redis_obj.get)
    mock_redis_method.hget = Mock(side_effect=mock_redis_obj.hget)
    mock_redis_method.set = Mock(side_effect=mock_redis_obj.set)
    mock_redis_method.hset = Mock(side_effect=mock_redis_obj.hset)
    mock_redis_method.exists = Mock(side_effect=mock_redis_obj.exists)

    # StrictRedis mock return_values is set as above mock_redis_method.
    mock_redis.return_value = mock_redis_method
```

Voila! it's done 🍸. We have successfully mocked redis.

##Bonus Content ✅
We can similarly mock requests library

```python
@patch("requests.get")
@patch("requests.post")
def test_02_external_api_calls(self, *mocks):
    # request and response are mapped in a dict
    request_response_dict = {
        "https://dummy-host?key=foo":  (
            {"key": "foo"}, 200
        ),
        "https://dummy-host?key=bar": (
            {"message": "Not Found"}, 404
        )
    }

    class MockResponse:
        def __init__(self, json_data, status_code):
            self.json_data = json_data
            self.status_code = status_code

        # request.json()
        def json(self):
            return self.json_data

    def mock_request_method(url, *args, **kwargs):
        response = request_response_dict[url]
        return MockResponse(response[0], response[1])
        
    # get and post method return similar kind of response. 
    mocks[0].side_effect = mock_request_method
    mocks[1].side_effect = mock_request_method
```

Cheers 🍻