Search code examples
pythondjangodjango-rest-frameworkpython-unittestpython-unittest.mock

Should Response be imported or mocked when using unittest.mock


I'm learning unittest and unittest.mock with the latter not quite clicking.

I'm mocking the response of an end-point and had the following:

import unittest
from unittest.mock import patch
from rest_framework.test import APIClient

class CoreTestCase(unittest.TestCase):
    
    @patch('core.views.CoreViewSet.list')
    def test_response(self, mock_get):
        mock_get.return_value = [{'hello': 'world'}]
        client = APIClient()
        response = client.get('/core/')
        print(response.data)

Which resulted in the following error:

AssertionError: Expected a `Response`, `HttpResponse` or `HttpStreamingResponse` to be returned from the view, but received a `<class 'list'>`

Made sense because DRF uses Response.

To get around the error I modified to:

import unittest
from unittest.mock import patch
from rest_framework.test import APIClient
from rest_framework.response import Response

class CoreTestCase(unittest.TestCase):
    
    @patch('core.views.CoreViewSet.list')
    def test_response(self, mock_get):
        mock_get.return_value = Response([{'hello': 'world'}])
        client = APIClient()
        response = client.get('/core/')
        print(response.data)

Which achieved the desired result and I was just passing in an array of objects (or list of dicts).

However, I'm not sure this was the correct way of handling this and if Mock() should have been used instead. I can honestly say I haven't been able to figure out how to use Mock() or MagicMock() correctly, or find a tutorial that explains it clearly, so my attempts at using it with:

mock_get.return_value = Mock([{'hello': 'world'}])

Just resulted in:

TypeError: 'module' object is not callable

Any suggestions about how this test should be implemented?


Solution

  • Cause

    AssertionError: Expected a Response, HttpResponse or HttpStreamingResponse to be returned from the view, but received a <class 'list'>

    This error comes from isinstance assertion here.

    Solution

    If you want to use Mock or MagicMock to mock Response and pass isinstance check, you can use spec or spec_set parameter when creating a mock response.

    class CoreTestCase(unittest.TestCase):
        
        @patch('core.views.CoreViewSet.list')
        def test_response(self, mock_get):
            mock_get.return_value = Mock(spec=Response, data=[{'hello': 'world'}])
            ...
    

    Notes

    When creating a Mock or Magic, you should pass the property name of what you want to mock as keyword arguments. In this case, you want to mock Response.data, therefore you should set Mock(data=<DATA_VALUE>). Otherwise, the value will be passed to Mock positional arguments.

    The official python documentation also provides clear examples of how to use mock module here.