Search code examples
pythondjangounit-testingdjango-rest-frameworkpython-unittest.mock

Mocking a request object to pass to ViewSet create() method


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?


Solution

  • 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.