Pytest with Django

Dhiraj Patra - May 31 - - Dev Community

Steps and code to set up Django Rest Framework (DRF) test cases with database mocking.

  1. Set up Django and DRF

Install Django and DRF:


pip install django djangorestframework

Enter fullscreen mode Exit fullscreen mode

Create a Django project and app:


django-admin startproject projectname

cd projectname

python manage.py startapp appname

Enter fullscreen mode Exit fullscreen mode
  1. Define Models, Serializers, and Views

models.py (appname/models.py):


from django.db import models



class Item(models.Model):

    name = models.CharField(max_length=100)

    description = models.TextField()

Enter fullscreen mode Exit fullscreen mode

serializers.py (appname/serializers.py):


from rest_framework import serializers

from .models import Item



class ItemSerializer(serializers.ModelSerializer):

    class Meta:

        model = Item

        fields = '__all__'

Enter fullscreen mode Exit fullscreen mode

views.py (appname/views.py):


from rest_framework import viewsets

from .models import Item

from .serializers import ItemSerializer



class ItemViewSet(viewsets.ModelViewSet):

    queryset = Item.objects.all()

    serializer_class = ItemSerializer

Enter fullscreen mode Exit fullscreen mode

urls.py (appname/urls.py):


from django.urls import path, include

from rest_framework.routers import DefaultRouter

from .views import ItemViewSet



router = DefaultRouter()

router.register(r'items', ItemViewSet)



urlpatterns = [

    path('', include(router.urls)),

]

Enter fullscreen mode Exit fullscreen mode

projectname/urls.py:


from django.contrib import admin

from django.urls import path, include



urlpatterns = [

    path('admin/', admin.site.urls),

    path('api/', include('appname.urls')),

]

Enter fullscreen mode Exit fullscreen mode
  1. Migrate Database and Create Superuser

python manage.py makemigrations appname

python manage.py migrate

python manage.py createsuperuser

python manage.py runserver

Enter fullscreen mode Exit fullscreen mode
  1. Write Test Cases

tests.py (appname/tests.py):


from django.urls import reverse

from rest_framework import status

from rest_framework.test import APITestCase

from .models import Item

from .serializers import ItemSerializer



class ItemTests(APITestCase):



    def setUp(self):

        self.item1 = Item.objects.create(name='Item 1', description='Description 1')

        self.item2 = Item.objects.create(name='Item 2', description='Description 2')



    def test_get_items(self):

        url = reverse('item-list')

        response = self.client.get(url, format='json')

        items = Item.objects.all()

        serializer = ItemSerializer(items, many=True)

        self.assertEqual(response.status_code, status.HTTP_200_OK)

        self.assertEqual(response.data, serializer.data)



    def test_create_item(self):

        url = reverse('item-list')

        data = {'name': 'Item 3', 'description': 'Description 3'}

        response = self.client.post(url, data, format='json')

        self.assertEqual(response.status_code, status.HTTP_201_CREATED)

        self.assertEqual(Item.objects.count(), 3)

        self.assertEqual(Item.objects.get(id=3).name, 'Item 3')



    def test_update_item(self):

        url = reverse('item-detail', kwargs={'pk': self.item1.id})

        data = {'name': 'Updated Item 1', 'description': 'Updated Description 1'}

        response = self.client.put(url, data, format='json')

        self.assertEqual(response.status_code, status.HTTP_200_OK)

        self.item1.refresh_from_db()

        self.assertEqual(self.item1.name, 'Updated Item 1')



    def test_delete_item(self):

        url = reverse('item-detail', kwargs={'pk': self.item2.id})

        response = self.client.delete(url, format='json')

        self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT)

        self.assertEqual(Item.objects.count(), 1)

Enter fullscreen mode Exit fullscreen mode
  1. Run Tests

python manage.py test

Enter fullscreen mode Exit fullscreen mode

This setup provides a basic Django project with DRF and test cases for CRUD operations using the database. The test cases mock the database operations, ensuring isolation and consistency during testing.

Now diving into some more feature tests with Mock, patch etc.

Here are steps and code to write Django Rest Framework (DRF) test cases using mocking and faking features for scenarios like credit card processing.

  1. Set up Django and DRF

Install Django and DRF:


pip install django djangorestframework

Enter fullscreen mode Exit fullscreen mode

Create a Django project and app:


django-admin startproject projectname

cd projectname

python manage.py startapp appname

Enter fullscreen mode Exit fullscreen mode
  1. Define Models, Serializers, and Views

models.py (appname/models.py):


from django.db import models



class Payment(models.Model):

    card_number = models.CharField(max_length=16)

    card_holder = models.CharField(max_length=100)

    expiration_date = models.CharField(max_length=5)

    amount = models.DecimalField(max_digits=10, decimal_places=2)

    status = models.CharField(max_length=10)

Enter fullscreen mode Exit fullscreen mode

serializers.py (appname/serializers.py):


from rest_framework import serializers

from .models import Payment



class PaymentSerializer(serializers.ModelSerializer):

    class Meta:

        model = Payment

        fields = '__all__'

Enter fullscreen mode Exit fullscreen mode

views.py (appname/views.py):


from rest_framework import viewsets

from .models import Payment

from .serializers import PaymentSerializer



class PaymentViewSet(viewsets.ModelViewSet):

    queryset = Payment.objects.all()

    serializer_class = PaymentSerializer

Enter fullscreen mode Exit fullscreen mode

urls.py (appname/urls.py):


from django.urls import path, include

from rest_framework.routers import DefaultRouter

from .views import PaymentViewSet



router = DefaultRouter()

router.register(r'payments', PaymentViewSet)



urlpatterns = [

    path('', include(router.urls)),

]

Enter fullscreen mode Exit fullscreen mode

projectname/urls.py:


from django.contrib import admin

from django.urls import path, include



urlpatterns = [

    path('admin/', admin.site.urls),

    path('api/', include('appname.urls')),

]

Enter fullscreen mode Exit fullscreen mode
  1. Migrate Database and Create Superuser

python manage.py makemigrations appname

python manage.py migrate

python manage.py createsuperuser

python manage.py runserver

Enter fullscreen mode Exit fullscreen mode
  1. Write Test Cases with Mocking and Faking

tests.py (appname/tests.py):


from django.urls import reverse

from rest_framework import status

from rest_framework.test import APITestCase

from unittest.mock import patch

from .models import Payment

from .serializers import PaymentSerializer



class PaymentTests(APITestCase):



    def setUp(self):

        self.payment_data = {

            'card_number': '4111111111111111',

            'card_holder': 'John Doe',

            'expiration_date': '12/25',

            'amount': '100.00',

            'status': 'Pending'

        }

        self.payment = Payment.objects.create(**self.payment_data)



    @patch('appname.views.PaymentViewSet.create')

    def test_create_payment_with_mock(self, mock_create):

        mock_create.return_value = self.payment



        url = reverse('payment-list')

        response = self.client.post(url, self.payment_data, format='json')



        self.assertEqual(response.status_code, status.HTTP_201_CREATED)

        self.assertEqual(response.data['card_number'], self.payment_data['card_number'])



    @patch('appname.views.PaymentViewSet.perform_create')

    def test_create_payment_fake_response(self, mock_perform_create):

        def fake_perform_create(serializer):

            serializer.save(status='Success')



        mock_perform_create.side_effect = fake_perform_create



        url = reverse('payment-list')

        response = self.client.post(url, self.payment_data, format='json')



        self.assertEqual(response.status_code, status.HTTP_201_CREATED)

        self.assertEqual(response.data['status'], 'Success')



    def test_get_payments(self):

        url = reverse('payment-list')

        response = self.client.get(url, format='json')

        payments = Payment.objects.all()

        serializer = PaymentSerializer(payments, many=True)

        self.assertEqual(response.status_code, status.HTTP_200_OK)

        self.assertEqual(response.data, serializer.data)



    @patch('appname.views.PaymentViewSet.retrieve')

    def test_get_payment_with_mock(self, mock_retrieve):

        mock_retrieve.return_value = self.payment



        url = reverse('payment-detail', kwargs={'pk': self.payment.id})

        response = self.client.get(url, format='json')



        self.assertEqual(response.status_code, status.HTTP_200_OK)

        self.assertEqual(response.data['card_number'], self.payment_data['card_number'])



    @patch('appname.views.PaymentViewSet.update')

    def test_update_payment_with_mock(self, mock_update):

        mock_update.return_value = self.payment

        updated_data = self.payment_data.copy()

        updated_data['status'] = 'Completed'



        url = reverse('payment-detail', kwargs={'pk': self.payment.id})

        response = self.client.put(url, updated_data, format='json')



        self.assertEqual(response.status_code, status.HTTP_200_OK)

        self.assertEqual(response.data['status'], 'Completed')



    @patch('appname.views.PaymentViewSet.destroy')

    def test_delete_payment_with_mock(self, mock_destroy):

        mock_destroy.return_value = None



        url = reverse('payment-detail', kwargs={'pk': self.payment.id})

        response = self.client.delete(url, format='json')



        self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT)

        self.assertEqual(Payment.objects.count(), 0)

Enter fullscreen mode Exit fullscreen mode
  1. Run Tests

python manage.py test

Enter fullscreen mode Exit fullscreen mode

This setup uses unittest.mock.patch to mock the behavior of various viewset methods in DRF, allowing you to simulate different responses without hitting the actual database or external services.

. . . . . . . . . . . . . . . . . . . . . . . .