How to Add Unit Testing in Django?

Published On: 13/10/2022 | Category: Django


Hi Dev,

I am going to explain you example of how to implement unit testing in django?. I would like to share with you how to write unittest in django. I explained simply step by step how to write and run unittest in django. Here you will learn how to run unittest in django. you will do the following things for how to add unit testing in django.

The process of developing software must include testing. Many programmers skip this step and test their code by hand instead.

As the size of the programme increases, manual code testing gets tiresome. Every component you add to your programme must function properly without affecting the operation of other features, and unit tests make sure this happens.

In this straightforward illustration, we'll test:

  • test_api_view.py
  • test_forms.py
  • test_models.py
  • test_views.py

Here i explained simply step by step example of how to write unittest in django.

Step 1: Create a Project

In this step, we’ll create a new django project using the django-admin. Head back to your command-line interface and run the following command:

django-admin startproject example

Step 2: Create a App

Now we'll create a single app called core to store a list of post names. We're keeping things intentionally basic. Stop the local server with Control+c and use the startapp command to create this new app.

python3 manage.py startapp core

Step 3: Install a Django REST Framework

First of all let’s install django and djangorestframework which are the necessary Python libraries.Django Rest Framework.

pip install djangorestframework

Step 4: Update setting.py

Then update INSTALLED_APPS within our settings.py file to notify Django about the app, in setting.py file so our setting file look like this..

settings.py

....
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'core',
    'rest_framework',
]

Step 5: Create a Model

Now go for the models we will We'll call our single model Student and it will have define below all fields: first_name and last_name is a full name of string return. And finally set __str__ to display the name of the student in admin interface.

core/models.py
from django.db import models
from django.urls import reverse

# Create your models here.
class Student(models.Model):
    first_name = models.CharField(max_length=50)
    last_name = models.CharField(max_length=50)
    reg_number = models.CharField(max_length=50)
    date_of_admission = models.DateField(null=True, blank=True)

    class Meta:
        ordering = ['-id']
        
    def get_absolute_url(self):
        return reverse("student_detail", args=[str(self.id)])

    def __str__(self):
        return f"Name: {self.first_name} {self.last_name}"

Ok, all set. We can engender a migrations file for this change, then integrate it to our database via migrate.

python manage.py makemigrations
python manage.py migrate

Step 6: Creating the Serializers

In this step, we need to create Serializers allow complex data such as querysets and model instances to be converted to native Python datatypes that can then be easily rendered into JSON, XML or other content types. Serializers also provide deserialization, allowing parsed data to be converted back into complex types, after first validating the incoming data. Let’s start creating a serializer.

core/serializers.py
from rest_framework import serializers
from .models import Student

class StudentSerializer(serializers.ModelSerializer):

    class Meta:
        model = Student
        fields = "__all__"

The bit of code above transforms the Student model into a JSON and back again. Transmission of JSON data via HTTP is simple. The data is transformed to JSON for this reason.

Step 7: Creating the Views

In this step, we need to configure views. The StudentListView will just return a list of all student. open the core/views.py file and add:

core/views.py
from django.shortcuts import render
from .models import Student
from django.core.paginator import Paginator
from django.views.generic import DetailView
from django.views.generic.list import ListView

# Create your views here.
class StudentListView(ListView):
    model = Student
    template_name = 'student.html'
    paginate_by = 10
        
        
class StudentView(DetailView):
    model = Student    

Step 8: Creating API Views

Add the following code snippets to a new python file called api views.py in the testing application.

core/views.py
from rest_framework import generics
from .models import Student
from .serializers import StudentSerializer

class CreateStudentApiView(generics.CreateAPIView):
     queryset = Student.objects.all()
     serializer_class = StudentSerializer  

A class that enables the creation of students using a REST API is present in the aforementioned code snippets.

Step 9: Creating the Templates

Next, then with your text editor create new templates files: core/templates/student.html file and the add:

core/templates/student.html

<!doctype html>
<html lang="en">

<head>
    <!-- Required meta tags -->
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
    <!-- Bootstrap CSS -->
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css"
        integrity="sha384-MCw98/SFnGE8fJT3GXwEOngsV7Zt27NXFoaoApmYm81iuXoPkFOJwJ8ERdknLPMO" crossorigin="anonymous">
    <title>Tuts-Station.com</title>
</head>

<body>
    <div class="container" style="margin-top: 100px;">
        <table class="table">
            <thead class="thead-dark">
                <tr>
                    <th scope="col">#</th>
                    <th scope="col">First</th>
                    <th scope="col">Last</th>
                    <th scope="col">Reg. No.</th>
                </tr>
            </thead>
            <tbody>
                <!-- prints out the students details in a table -->
                {% for student in student_list %}
                <tr>
                    <th scope="row">{{ student.id }}</th>
                    <td>{{ student.first_name }}</td>
                    <td>{{ student.last_name }}</td>
                    <td>{{ student.reg_number }}</td>
                </tr>
                {% endfor %}
            </tbody>
        </table>
    </div>


    <!-- Optional JavaScript -->
    <!-- jQuery first, then Popper.js, then Bootstrap JS -->
    <script src="https://code.jquery.com/jquery-3.3.1.slim.min.js"
        integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo"
        crossorigin="anonymous"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.3/umd/popper.min.js"
        integrity="sha384-ZMP7rVo3mIykV+2+9J3UJ46jBk0WLaUAdn689aCwoqbBJiSnjAK/l8WvCWPIPm49"
        crossorigin="anonymous"></script>
    <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/js/bootstrap.min.js"
        integrity="sha384-ChfqqxuZUCnJSK3+MXmPNIyE6ZbWh2IMqE241rYiqJxyMiZ6OW/JmZQ5stwEULTy"
        crossorigin="anonymous"></script>
</body>

</html>

Step 10: Writing Unit Tests

Writing tests for our beliefs will be the first step. Make a new tests-themed Python package and include it in the testing application.

Testing Views:

core/tests_views.py
from django.test import TestCase
from core.models import Student
from django.urls import reverse

class StudentListViewTest(TestCase):

    @classmethod
    def setUpTestData(cls):
        number_of_students = 30
        for student_id in range(number_of_students):
            Student.objects.create(first_name=f"John{student_id}", last_name=f"Doe{student_id}")

            
    def test_url_exists(self):
        response = self.client.get("/students")
        self.assertEqual(response.status_code, 200)


    def test_url_accessible_by_name(self):
        response = self.client.get(reverse('students'))
        self.assertEqual(response.status_code, 200)


    def test_view_uses_correct_template(self):
        response = self.client.get(reverse('students'))
        self.assertEqual(response.status_code, 200)
        self.assertTemplateUsed(response, 'student.html')


    def test_pagination_is_correct(self):
        response = self.client.get(reverse('students'))
        self.assertEqual(response.status_code, 200)
        self.assertTrue('is_paginated' in response.context)
        self.assertTrue(response.context['is_paginated'] is True)
        self.assertEqual(len(response.context['student_list']), 10)

Testing Models:

core/tests_views.py
from core.models import Student
from django.urls import reverse
from django.test import TestCase

class StudentModelTestcase(TestCase):

    @classmethod
    def setUpTestData(cls):
        Student.objects.create(first_name="Bhavesh", last_name="Sonagra", reg_number="1111")


    def test_string_method(self):
        student = Student.objects.get(id=1)
        expected_string = f"Name: {student.first_name} {student.last_name}"
        self.assertEqual(str(student), expected_string)

    def test_get_absolute_url(self):
        student = Student.objects.get(id=1)
        self.assertEqual(student.get_absolute_url(), "/students/1")
        

Testing Forms:

First of all in this step we will create a new file called forms.py and create a Django form you need to use Django Form Class

core/forms.py
from django import forms
class StudentForm(forms.ModelForm):

    class Meta:
        model = Student
        fields = ('first_name', 'last_name', 'reg_number')
core/tests_views.py
from core.models import Student
from django.urls import reverse
from django.test import TestCase
from core.forms import StudentForm

class StudentModelTestcase(TestCase):

    @classmethod
    def test_valid_form(self):
        w = Student.objects.create(first_name="Bhavesh", last_name="Sonagra", reg_number="1111")
        data = {'first_name': w.first_name, 'last_name': w.last_name, 'reg_number': w.reg_number}
        form = StudentForm(data=data)
        self.assertTrue(form.is_valid(),True)

    def test_invalid_form(self):
        w = Student.objects.create(first_name="Bhavesh", last_name="", reg_number="")
        data = {'first_name': w.first_name, 'last_name': w.last_name, 'reg_number': w.reg_number}
        form = StudentForm(data=data)
        self.assertFalse(form.is_valid()) 

Testing API Views:

core/tests_views.py
from core.models import Student
from django.urls import reverse
from rest_framework.test import APITestCase

class StudentSerializerTestCase(APITestCase):

    def student_creation_test(self):
        payload = {
            "first_name": "Bhavesh",
            "last_name": "Sonagra",
            "reg_number": "123456",
            "date_of_admission": datetime.date.today()
        }

        response = self.client.post(reverse("student_create"), payload)
        self.assertEqual(status.HTTP_201_CREATED, response.status_code)      

Step 11: Creating URLs

In this section, we need a urls.py file within the core app however Django doesn't create one for us with the startapp command. Create core/urls.py with your text editor and paste below code.

core/urls.py
from django.urls import path
from . import views
from .api_view import CreateStudentApiView

urlpatterns = [
    path('students', views.StudentListView.as_view(), name="students"),
    path('students/create', CreateStudentApiView.as_view(), name="create_student"),
    path('students/<int:id>', views.StudentView.as_view(), name="student_detail"),
]

Next, we require to add a URL path for our example app which can be done by importing include and setting a path for it.

example/urls.py
from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path('admin/', admin.site.urls),
    path('', include('core.urls')),
]

Run the Test

Run the command shown below in the terminal's current working directory to run our tests.

python manage.py test
Output
Creating test database for alias 'default'...
System check identified no issues (0 silenced).
........
----------------------------------------------------------------------
Ran 8 tests in 0.032s

OK
Destroying test database for alias 'default'...

I hope it will help you....

Happy Coding!