Search code examples
pythondjangodjango-rest-frameworkpython-unittest

How to check if there was a call of service function from APIView?


I'm trying to implement the service layer in my API. Now im trying to test service function call from APIView. When i'm sending POST request with test named 'test_view_calls_service', i have a 201 status code, that means that my author is created. Also test is passing with this check:

 self.assertTrue(Author.objects.filter(**self.data).exists())

But test still dont see a call of service function from the APIView

views.py

class AuthorListApiView(APIView):
    permission_classes = [permissions.IsAuthenticated]

    class InputSerializer(serializers.Serializer):
        name = serializers.CharField()

    def post(self, request):
        serializer = self.InputSerializer(data=request.data)
        serializer.is_valid(raise_exception=True)

        create_author(**serializer.validated_data)

        return Response(serializer.data, status=status.HTTP_201_CREATED)

services.py

def create_author(name: str) -> Author:
    """
    Create a Author model object.
    """
    author = Author(name=name)

    author.full_clean()
    author.save()

    return author

test_view.py

from unittest import mock

from django.urls import reverse
from faker import Faker
from test_plus.test import TestCase as PlusTestCase

faker = Faker()


class AuthorListApiViewTest(PlusTestCase):
    def setUp(self):
        self.url = reverse('authors')
        self.data = {
            'name': faker.pystr(max_chars=20)
        }
        self.user = self.make_user('user')

    def test_api_view_can_be_accessed(self):
        self.client.get(self.url)
        self.response_200

    def test_api_view_can_create(self):
        self.client.post(self.url, data=self.data)
        self.response_201

    @mock.patch('books.services.create_author')
    def test_view_calls_service(self, service_mock):
        with self.login(self.user):
            response = self.client.post(self.url, data=self.data)
            print(response.status_code)
        self.assertTrue(Author.objects.filter(**self.data).exists()) <--- This check is also passed
        service_mock.assert_called_once_with(**self.data)

AssertionError:

 Found 8 test(s).
 Creating test database for alias 'default'...
 System check identified no issues (0 silenced).
 ..201
 F.....
======================================================================
 FAIL: test_view_calls_service 
(books.tests.test_views.AuthorListApiViewTest)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "c:\users\user\appdata\local\programs\python\python38- 
32\lib\unittest\mock.py", line 1342, in patched
  return func(*newargs, **newkeywargs)
  File "F:\DeepMind\books_api\books\tests\test_views.py", line 34, in 
    test_view_calls_service
    service_mock.assert_called_once_with(**self.data)
  File "c:\users\user\appdata\local\programs\python\python38- 
32\lib\unittest\mock.py", line 918, in assert_called_once_with
  raise AssertionError(msg)

AssertionError: Expected 'create_author' to be called once. Called 0 times.

----------------------------------------------------------------------
Ran 8 tests in 1.626s

FAILED (failures=1)
Destroying test database for alias 'default'...

Solution

  • You patch the wrong module. create_author should be patched where it's lookup under test not where it is declared.

    # test_view.py
    class AuthorListApiViewTest(PlusTestCase):
    
        @mock.patch('books.views.create_author')
        def test_view_calls_service(self, service_mock):
            ...
    
    

    Also, using patch here without passing any argument to patch function will completely replace the real create_author function. If you only want to record what arguments are being passed through create_author function while passing the call to the real function, you can use wraps argument of patch decorator.

    # test_view.py
    class AuthorListApiViewTest(PlusTestCase):
    
        @mock.patch('books.views.create_author', wraps=create_author)
        def test_view_calls_service(self, service_mock):
            ...
    
    

    Further Readings