1

I'm learning unittest and unittest.mock, and struggling with the concepts and implementations primarily with mock.

For context, what I'm playing with is a Django / DRF API and Redis. I'm trying to write tests which require mocking the Redis calls.

Here is the test I'm working on:

# tests.py

import unittest
from unittest.mock import patch

from core.views import KeysViewSet


class KeysViewSetTestCase(unittest.TestCase):

    def setUp(self):
        self.json_object = {'key': 'hello', 'value': 'world'}
        self.view = KeysViewSet()

    def test_create(self):
        with patch('core.views.RedisUtil.create') as mocked_create:
            mocked_create.return_value.data = True

            created = self.view.create(self.json_object)

The views.py:

# viefws.py

# Third party imports
from rest_framework import status, viewsets
from rest_framework.response import Response

# Croner imports
from .serializers import KeysSerializer

# Import Redis
from .utils import RedisUtil


class KeysViewSet(viewsets.ViewSet):
    """
    METHOD      URI                 DESCRIPTION    
    GET         /api/keys/<:key>/   Returns specific value from key
    POST        /api/keys/          Creates a new key/value
    DELETE      /api/keys/<:key>/   Deletes a specific key
    """

    def __init__(self, *args, **kwargs):
        """
        Instantiate the RedisUtil object.
        """
        self.redis_util = RedisUtil()

    def create(self, request):
        """
        Creates a key/pair in the Redis store.
        """
        print(request)
        # Serialize the request body
        serializer = KeysSerializer(data=request.data)

        # If valid, create the key/value in Redis; if not send error message
        if serializer.is_valid():
            return Response(self.redis_util.create(serializer.data))
        else:
            return Response(serializer.errors,
                            status=status.HTTP_400_BAD_REQUEST)

And the utils.py that handles the Redis actions:

# utils.py

# Django imports
from django.conf import settings

# Redis imports
import redis


class RedisUtil:
    """
    Instantiates the Redis object and sets the connection params.
    """
    def __init__(self):
        self.redis_instance = redis.StrictRedis(
            host=settings.REDIS_HOST,
            port=settings.REDIS_PORT
        )

    def create(self, data):
        """
        Creates the key/value pair from request body.
        """
        return self.redis_instance.set(data['key'], data['value'])

The error I'm getting is the following:

Found 1 test(s).
System check identified no issues (0 silenced).
{'key': 'hello', 'value': 'world'}
E
======================================================================
ERROR: test_create (core.tests.KeysViewSetTestCase)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/Users/cjones/Projects/test/kv-store/api/src/core/tests.py", line 19, in test_create
    created = self.view.create(self.json_object)
  File "/Users/cjones/Projects/test/kv-store/api/src/core/views.py", line 32, in create
    serializer = KeysSerializer(data=request.data)
AttributeError: 'dict' object has no attribute 'data'

----------------------------------------------------------------------
Ran 1 test in 0.001s

FAILED (errors=1)

I know what I'm getting this error: the KeysViewSet().create() in views.py is expecting the object I'm passing to be at request.data = {} and it isn't.

Resolving it is what I'm trying to figure out.

I know there is the request library, but not sure I would need to import it just for this. DRF has several options including APIRequestFactory, but those will try to spin up a database and create an error as it will try to connect to Redis which it won't be able to.

How should I go about resolving this issue?

cjones
  • 8,384
  • 17
  • 81
  • 175

1 Answers1

1

What I ended up doing to resolve the issue was the following:

# test.py

import unittest
from unittest.mock import patch

from core.views import KeysViewSet


class KeysViewSetTestCase(unittest.TestCase):

    def setUp(self):
        self.json_object = {'key': 'hello', 'value': 'world'}
        self.view = KeysViewSet()
        class Request:
            def __init__(self, data):
                self.data = data
        self.request = Request(self.json_object)


    def test_create(self):
        with patch('core.views.RedisUtil.create') as mocked_create:
            mocked_create.return_value.data = True

            created = self.view.create(self.request)

That being said, I'm not sure that this is a desirable solution so I'm reluctant to accept it as the correct answer. Looking forward to feedback.

cjones
  • 8,384
  • 17
  • 81
  • 175
  • 1
    I think you can consider using a `unittest.mock.Mock` object for your request class instead. You can set return values, side effects (such as raising an exception) and custom attributes on the mock object too. Custom attributes can be passed to the constructor of the `Mock` object so you can do something like this: `created = self.view.create(Mock(data=self.json))` – KuroiKuro Feb 11 '22 at 10:43