This blog comprises of 3 parts:
- Part 1 – Fundamentals of Integration Testing with Django Rest Framework (This Blog)
- Part 2 – Django Token-Based Authentication Test Guidelines
- Part 3 – How to Mock Methods in Django Tests
We hope you find this blog useful. In this blog series, we will explore integration testing with the Django Rest Framework in detail, as the goal is to enhance your understanding of testing in Django and help you build robust applications.
Introduction
In the world of web development, integration testing plays a vital role in ensuring the reliability and functionality of APIs. This article dives into integration testing with the Django Rest Framework, a powerful toolset for building RESTful APIs in Django.
Without proper integration testing, several issues can arise, impacting the performance, reliability, and security of Django Rest Framework APIs. Inconsistent data flow may lead to incorrect or incomplete data being transmitted through the APIs, while changes to API endpoints or data models can break compatibility with other services.
Performance bottlenecks may occur due to inefficient API implementations or unexpected interactions with external systems. To avoid all these things, integration testing is a must-have in any enterprise-level project.
Step-by-step Approach
The article begins by explaining the significance of testing API endpoints without authentication. It explores how to write integration tests for unauthenticated endpoints using the Django Rest Framework’s test client.
Part 1: Fundamentals of Integration Testing with Django Rest Framework
You must specify a class that derives from TestCase in order to construct a test case in your Django project.
The following techniques from TestCase will help us while we test:
- setUp: This function will be called before the execution of each unit test (methods in which you will be testing your Django views)
- setUpTestData: This function can be used to define data for the whole test case. That means that this function automatically rolls back the changes in the database after the finalisation of all the unit tests in the test case. This is mainly used to load data to your test database.
- RequestFactory: RequestFactory provides a way to generate a request instance that can be used as the first argument to any view. This means you can test a view function the same way as you would test any other function
Example
Let’s begin by configuring the fundamental CRUD APIs in Django for a model. The Employee model will be used as an example. The code for the model, serializer, and views is provided below:
Model
class Employee(models.Model):
id = models.UUIDField(primary_key=True)
name = models.CharField(max_length=100, null=False)
designation = models.TextField(null=False)
is_active = models.BooleanField(default=True)
Serializer
class EmployeeSerializer(serializers.ModelSerializer):
class Meta:
model = Employee
fields = ['all']
Views
class EmployeeCollectionView(generics.ListCreateAPIView):
name = 'employee-collection-view'
queryset = Employee.objects.all()
serializer_class = EmployeeSerializer
class EmployeeMemberView(generics.RetrieveUpdateDestroyAPIView):
name = 'employee-member-view'
queryset = Employee.objects.all()
serializer_class = EmployeeSerializer
def get_object(self):
return Employee.objects.get(id=self.kwargs['pk'])
We are now ready to write integration tests as these elements are in place. We’ll begin with integration testing without authentication for the purposes of this article.
Tests
For each API endpoint, use RequestFactory to generate a request object. The setUp method initialises an instance of RequestFactory, which will be used to create the requests.
In each test method, create the appropriate request object using self.factory and then call the corresponding view using as_view(), passing the request object and any required parameters.
Use the assertions to validate the response status code and perform any necessary data validation.
Example
from django.test import TestCase, RequestFactory
## Import views/seralizers as needed
class EmployeeTest(TestCase):
def setUp(self):
self.factory = RequestFactory()
@classmethod
def setUpTestData(cls):
cls.emp1 = Employee.objects.create(name='John', designation='CTO')
cls.emp2 = Employee.objects.create(name='Roy', designation='CEO')
def test_get(self):
request = self.factory.get(BASE_URL)
response = EmployeeCollectionView.as_view()(request)
self.assertEqual(response.status_code, 200)
self.assertEqual(len(response.data['results']), 2)
def test_post(self):
payload = {
'name': 'Andy',
'designation': 'Manager'
}
request = self.factory.post(BASE_URL, data=payload)
response = EmployeeCollectionView.as_view()(request)
self.assertEqual(response.status_code, 201)
self.assertEqual(len(response.data['name']), payload['name'])
self.assertEqual(len(response.data['designation']), payload['designation'])
def test_retrieve(self):
request = self.factory.get(f'{URL}/{self.emp1.id}')
response = EmployeeMemberView.as_view()(request)
self.assertEqual(response.status_code, 200)
self.assertEqual(response.data['name'], self.emp1.name)
self.assertEqual(response.data['designation'], self.emp1.designation)
def test_delete(self):
request = self.factory.delete(f'{URL}/{self.emp2.id}')
response = EmployeeMemberView.as_view()(request)
self.assertEqual(response.status_code, 200)
def test_patch(self):
payload = {
'designation': 'Tech Head'
}
request = self.factory.patch(f'{URL}/{self.emp1.id}', data=payload)
response = EmployeeMemberView.as_view()(request)
self.assertEqual(response.status_code, 200)
self.assertEqual(response.data['designation'], payload['designation'])
Conclusion
This blog covered the foundations of integration testing using the Django Rest Framework.
As we move forward in this series, we will delve deeper into integration testing, including testing authenticated endpoints, mocking responses, and ensuring robust API behaviour.
Author
Nikhil Chopdekar
Asso. Lead Engineer