Date:

Share:

Unit tests in Django | Code Underscored

Related Articles

Manually checking websites becomes more difficult as they expand in size. Not only is there more to check, but as component ratios become more complicated, a slight change in one area may affect other areas. The latter will require further testing to ensure that everything continues to operate and no errors are created as further changes are made.

Writing automated tests, which can be performed and trusted every time you make a change, is one of the methods to alleviate these problems. This guide demonstrates how to use Django’s testing framework to automate the testing of your site units.

In the software development process, testing is a crucial step. Many software developers skip this step and manually check their code.

As the scope of the program increases, manual code checking becomes more cumbersome. Unit testing makes sure that every component you add to your project functions as planned without interfering with the operation of other features.

Because a site is made up of many logical layers, such as managing HTTP requests, validating forms, and processing templates, testing it can be difficult. On the other hand, Django comes with a set of tools that make testing your web app simple. The Python unittest module is the preferred way to write tests in Django, while other test frameworks can be used.

This guide will guide you through a basic Django app that allows school principals to store information about accepted students. We will write unit tests for each of our app components.

Testing methodologies

Tests and testing procedures come in a variety of types, grades and categories. Here are the essential automated tests:

Unit tests

It is almost difficult to create sites that run flawlessly for the first time. As a result, you should check your web application to identify these errors and address them as soon as possible. It is customary to divide the tests into units that test specific capabilities of a web application to improve test efficiency. Unit testing is the term for this procedure. Because testing focuses on specific parts (units) of your project independent of other components, making it easier to spot mistakes.

Unit tests, in summary, are a type of test used to test the functionality of individual components, usually at the class and function level.

Regression tests

These are tests that recreate bugs from the past. Each check is performed once to make sure the bug has been fixed. It also ensures that it is not re-created due to further changes in the code.

Integration tests

Check how component groups interact when used together. Integration tests are aware of the interactions of required components but not necessarily of the basic activities of each component.

Creating the app

First, we will create a directory named django_unit_tests as follows

mkdir django_unit_tests && django_unit_tests

We will then create a virtual environment as follows.

Next, we will activate the virtual environment by running the following command.

source unittests_env/bin/activate

At this point, we can now install Django using pip in the following way.

pip install django==3.2.6

Now, we can create a Django application by running the following command.

django-admin startproject djangotesting

This will create a Django testing app

The most common way to organize a Django project is through applications. Larger projects are easier to handle as a result. We will start by running the command below to build a new Django app in our project.

First, we will change the directory to apply the test using the following command.

Because there is a manage.py file.

$ python manage.py startapp employee_testing

By using the command below, you can add the Django restframework to your application.

pip install djangorestframework

After installing djangorestframework, we need to add it to the INSTALLED_APPS list in the settings.py file, as shown below.

# settings.py

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

Models

In the models.py file, we will add the following code snippet.

# models.py

class Employee(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)

  def get_absolute_url(self):
    return reverse("employee-detail", args=[str(self.id)])

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

The Employee model is created in the database as an Employee table in the code snippet above. There are two methods in the model: –

get_absolute_url (self): Returns the URL to a specific employee private page.

str(Self): Returns a string representation of the employee’s first and last name.

Serial

Can you please create a new Python file called serializers.py in the test application and paste the code below?

# serializers.py

class EmployeeSerializer(ModelSerializer):
  class Meta:
    model = Employee
    fields = "all"

The previous line of code changes the Employee model to JSON and back. It is easy to send JSON data over HTTP. As a result, the data is being transformed into the JSON format.

Add the code snippet below to the views.py file.

class EmployeeListView(generic.ListView):
  model = Employee
  paginate_by = 10 # the number of employees to return in each page
  
class EmployeeView(generic.DetailView):
  model = Employee

There are two display classes in the code section above: –

The EmployeeListView returns a list of employees.

EmployeeView is a view that provides detailed information about an employee.

View API

In the test app, create a new python file named api_views.py and add the code snippets below.

# api_view.py

class CreateEmployeeApiView(generics.CreateAPIView):
  queryset = Employee.objects.all()
  serializer_class = EmployeeSerializer

A class is included in previous code snippets that allow employees to create a REST API.

URLs

Create a new urls.py file in the test app and add the code snippets below.

#employee_testing/urls.py

urlpatterns = [
 path('employees', EmployeeListView.as_view(), name="employees"),
 path('employee/create', CreateEmployeeApiView.as_view(), name="create-employee"),
 path('employee/<int:id>', EmployeeView.as_view(), name="employee-detail")
]

The paths to different views in the program are included in the code snippets above.

As shown below, edit the test application URLs in the root project’s urls.py file.

# djangotesting/urls.py

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

pattern

Create a new library named Templates in the project library. Next, create a new library named testing in the template library you just created. This folder will contain the template files of the test application.

Add the code snippet below to a new HTML file named werknemer_list.html in the Template Library test library.

<!-- testing/employee_list.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>Employee enrollment application</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">Employee First Name </th>
                    <th scope="col">Employee Last Name</th>
                    <th scope="col">Employee Reg. No.</th>
                </tr>
            </thead>
            <tbody>
                <!-- prints out the employees details in a table -->
                % for employee in employee_list %
                <tr>
                    <th scope="row"> employee.id </th>
                    <td> employee.first_name </td>
                    <td> employee.last_name </td>
                    <td> employee.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>

Follow the instructions below to check the program and confirm that everything is working properly.

$ python manage.py makemigrations
$ python manage.py migrate
$ python manage.py runserver

Unit Tests: How to Write Them

We will start by writing tests for our opinions. Create a new python package called tests in the testing app.

How to check views

Create a new python file named tests_views.py in the test package you just created.

Note: This is a common practice for test files, in the first place, the word tests.

Add the following code to the tests_views.py file you previously created.

# test_views.py

class EmployeeListViewTest(TestCase):
  
	@classmethod
	def setUpTestData(cls):
      number_of_employees = 30
      for employee_id in range(number_of_employees):
        Employee.objects.create(first_name=f"Johnemployee_id", last_name=f"Doeemployee_id")
        
    def test_url_exists(self):
      response = self.client.get("/employees")
      self.assertEqual(response.status_code, 200)

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

    def test_view_uses_correct_template(self):
        response = self.client.get(reverse('employees'))
        self.assertEqual(response.status_code, 200)
        self.assertTemplateUsed(response, 'testing/employee_list.html')

    def test_pagination_is_correct(self):
        response = self.client.get(reverse('employees'))
        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['employee_list']), 10)

The setUpTestData (cls) method is marked @classmethod because it is called first when the class is started. We create working objects in this function, store them in a temporary test database and use them throughout the test lesson.

The test_url_exists method initiates HTTP read to the specified path and checks for a successful response code.

Test_url_accessible_by_name techniques are used to see if a URL is available by name. First, create a URL from the specified name, send an HTTP request and review the response status code.

test_view_uses_correct_template – When visiting the specified path, this method confirms whether the correct template is loaded.

The test_pagination_is_correct method checks if the returned data is met. Otherwise, this test will fail.

Testing models

Could you please create a new file named test_models.py in the test package and paste the code snippets below?

# test_models.py

class EmployeeModelTestcase(TestCase):
  @classmethod
  def setUpTestData(cls):
    Employee.objects.create(first_name="Peter", last_name="John", reg_number="111b2")
    
  def test_string_method(self):
    employee = Employee.objects.get(id=1)
    expected_string = f"Name: employee .first_name employee .last_name"
    self.assertEqual(str(employee), expected_string)

  def test_get_absolute_url(self):
      employee = Employee.objects.get(id=1)
      self.assertEqual(employee.get_absolute_url(), "/employees/1")

In the test code above: –

The setUpTestData function defines the object to be used throughout the test class.

The test_string_method method checks whether the string is returned by the Employee model str The method is legitimate.

The test_get_absolute_url method verifies that the absolute URL of the model is correct.

Check views from APIs

Could you please create a new Python file named tests_api_view.py in the test package and paste the code snippets below?

# test_api_view.py

class EmployeeSerializerTestCase(APITestCase):
  
  def employee_creation_test(self):
    payload = 
    "first_name": "Tom",
    "last_name": "Ann",
    "reg_number": "Bob",
    "reg_date": datetime.date.today()
    
	response = self.client.post(reverse("employee-create"), payload)
	self.assertEqual(status.HTTP_201_CREATED, response.status_code)

There is only one method in the code snippet above: –

To test the endpoint for creating an employee, use employ_creation_test, we perform a POST request for employees / create an endpoint with the payload and then create a payload that contains all the data required to create an employee.

We used APITestCase from restframework instead of TestCase from Django in test_api_view.

Run the command below in the terminal in the current job directory to run our tests.

Fill in the source code for reference

Below are snippets of code for different sections for your reference for comparison in case you missed one of the sections while reading the article.

Definitions

# djangotesting/settings.py

"""
Django settings for djangotesting project.

Generated by 'django-admin startproject' using Django 3.2.6.

For more information on this file, see
https://docs.djangoproject.com/en/3.2/topics/settings/

For the full list of settings and their values, see
https://docs.djangoproject.com/en/3.2/ref/settings/
"""

from pathlib import Path

# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent


# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/3.2/howto/deployment/checklist/

# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = 'django-insecure-x6si2ioub15=a5+d5v_7!u-xh4+rs(aw#rq1fuut*vho534(^v'

# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True

ALLOWED_HOSTS = []


# Application definition

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

MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
]

ROOT_URLCONF = 'djangotesting.urls'

TEMPLATES = [
    
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [BASE_DIR / 'templates'],
        'APP_DIRS': True,
        'OPTIONS': 
            'context_processors': [
                'django.template.context_processors.debug',
                'django.template.context_processors.request',
                'django.contrib.auth.context_processors.auth',
                'django.contrib.messages.context_processors.messages',
            ],
        ,
    ,
]

WSGI_APPLICATION = 'djangotesting.wsgi.application'


# Database
# https://docs.djangoproject.com/en/3.2/ref/settings/#databases

DATABASES = 
    'default': 
        'ENGINE': 'django.db.backends.sqlite3',
        'NAME': BASE_DIR / 'db.sqlite3',
    



# Password validation
# https://docs.djangoproject.com/en/3.2/ref/settings/#auth-password-validators

AUTH_PASSWORD_VALIDATORS = [
    
        'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
    ,
    
        'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
    ,
    
        'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
    ,
    
        'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
    ,
]


# Internationalization
# https://docs.djangoproject.com/en/3.2/topics/i18n/

LANGUAGE_CODE = 'en-us'

TIME_ZONE = 'UTC'

USE_I18N = True

USE_L10N = True

USE_TZ = True


# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/3.2/howto/static-files/

STATIC_URL = '/static/'

# Default primary key field type
# https://docs.djangoproject.com/en/3.2/ref/settings/#default-auto-field

DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'

Employee_check

# djangotesting/urls.py

from django.contrib import admin
from django.urls import path, include

urlpatterns = [
 path('admin/', admin.site.urls),
 path('', include('employee_testing.urls'))
]
# employee_testing/views.py

from django.views import generic

from .models import Employee


class EmployeeListView(generic.ListView):
    model = Employee
    paginate_by = 10


class EmployeeView(generic.DetailView):
    model = Employee
# employee_testing/urls.py

from django.urls import path

from .api_view import CreateEmployeeApiView
from .views import EmployeeListView, EmployeeView

urlpatterns = [
    path('employee', EmployeeListView.as_view(), name="employees"),
    path('employee/create', CreateEmployeeApiView.as_view(), name="create-employee"),
    path('employee/<int:id>', EmployeeView.as_view(), name="employee-detail")
]
# employee_testing/serializer.py

from rest_framework.serializers import ModelSerializer

from .models import Employee


class EmployeeSerializer(ModelSerializer):
    class Meta:
        model = Employee
        fields = "__all__"
# employee_testing/models.py

from django.db import models
from django.urls import reverse


class Employee(models.Model):
    first_name = models.CharField(max_length=50)
    last_name = models.CharField(max_length=50)
    reg_number = models.CharField(max_length=50)
    reg_date = models.DateField(null=True, blank=True)

    def get_absolute_url(self):
        return reverse("employee-detail", args=[str(self.id)])

    def __str__(self):
        return f"Name: self.first_name self.last_name"
# employee_testing/api_view.py

from rest_framework import generics

from .models import Employee
from .serializer import EmployeeSerializer


class CreateEmployeeApiView(generics.CreateAPIView):
    queryset = Employee.objects.all()
    serializer_class = EmployeeSerializer
    

Tests

# tests/test_models.py

from django.test import TestCase

from djangotesting.employee_testing.models import Employee


class StudentModelTestcase(TestCase):
    @classmethod
    def setUpTestData(cls):
        Employee.objects.create(first_name="ann", last_name="brown", reg_number="4123")

    def test_string_method(self):
        employee = Employee.objects.get(id=1)
        expected_string = f"Name: employee.first_name employee.last_name"
        self.assertEqual(str(employee), expected_string)

    def test_get_absolute_url(self):
        employee = Employee.objects.get(id=1)
        self.assertEqual(employee.get_absolute_url(), "/employees/1")
# tests/tests_api_view.py

import datetime

from django.urls import reverse
from rest_framework import status
from rest_framework.test import APITestCase


class EmployeeSerializerTestCase(APITestCase):
    def student_creation_test(self):
        payload = 
            "first_name": "ann",
            "last_name": "brown",
            "reg_number": "4123",
            "reg_date": datetime.date.today()
        
        response = self.client.post(reverse("employee-create"), payload)
        self.assertEqual(status.HTTP_201_CREATED, response.status_code)
# tests/tests_views.py

from django.test import TestCase
from django.urls import reverse

from ..models import Employee


# test_views.py

class EmployeeListViewTest(TestCase):
    @classmethod
    def setUpTestData(cls):
        number_of_students = 30
        for student_id in range(number_of_students):
            Employee.objects.create(first_name=f"Johnstudent_id", last_name=f"Doestudent_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, 'testing/employee_list.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)

Summary

Writing a test code is not fun, and as a result, it is often done last (if at all) when building a website. However, it is necessary to ensure that your code is secure for distribution when making changes and cost-effective to maintain.

We covered how to write and run tests for your models, forms, and views in this article. But, most importantly, we’ve included a quick explanation of what you need to check out, which is often the most challenging part of the process when you’re just getting started.

There is still a lot to learn, but you should be able to write successful unit tests for your sites using what you have already learned.

Create a RESTful endpoint for our Employee app and add unit tests for the series and API views now that you know how to build unit tests for various components in the Django app.

Source

LEAVE A REPLY

Please enter your comment!
Please enter your name here

Popular Articles