Django testing - best practices and Examples
Tags: django, testing, python, codequality
Published: 2020-04-22 07:51:33

---
title: "Django testing - best practices and Examples"
published: true
description: "Learn How testing works in Django - best practices and Examples"
tags: django, testing, python, quality
cover_image: https://images.unsplash.com/photo-1574170609519-d1d8d4b71f60?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=1350&q=80
---


Originally posted on [my blog](https://rapidpython.com/django-testing-best-practices-and-examples/)

## Introduction

**Django** is a high level framework to build scalable web applications.
Django is used by companies like Google, Facebook, Pinterest, Nasa etc...

**Code quality** is very important in software engineering.
Testing is one of the best practices of a good developer, the reality is that everyone talk about it but in real word not everyone write tests 😁

We've many types of tests but in this tutorial i will cover two of them :

- **Unit Tests** Is a level of software testing where a section(unit) of an application meets the requirements, it focus on one specific function.
- **Integration Tests** Is a level of software testing where individual sections are combined and tested as a group, it combined multiple pieces of code and functionality.

## When and Why run tests

- Testing helps us structure good code, maintain large codebase and find bugs.
- Well written tests make your code - easy to debug, integrate and deploy.
- Well written tests save us lot of times.
- Whatever you're a solo developer or you work in a team you should write tests.

## Best practices

- If it can break, it should be tested. This includes models, views, forms, templates, validators, and so forth.
  Each test should generally only test one function.
- Keep it simple. You do not want to have to write tests on top of other tests.
- Run tests whenever code is PULLed or PUSHed from the repo and in the staging environment before PUSHing to production.
- When upgrading to a newer version of Django:
- upgrade locally,
- run your test suite,
- fix bugs,
- PUSH to the repo and staging, and then
- test again in staging before shipping the code.

[Learn more here](https://realpython.com/testing-in-django-part-1-best-practices-and-examples/)

## Setup

You can find the final source code [here](https://github.com/xarala221/django-phonebook)

- Create a virtual environment then activate it [Learn more](https://www.ousseynoudiop.com/python-how-to-setup-your-virtualenv-correctly/python-how-to-setup-your-virtualenv-correctly/)

```shell
pipenv shell
(phonebook_rest_api-9zIZds3o) ╭─username@username-Latitude-7480
```

- Run migrations

```shell
python manage.py migrate
Operations to perform:
 Apply all migrations: admin, auth, contact, contenttypes, sessions
```

- Run the application

```shell
python manage.py runserver
Watching for file changes with StatReloader
Performing system checks...

System check identified no issues (0 silenced).
April 21, 2020 - 18:37:31
Django version 3.0.2, using settings 'phonebook.settings'
Starting development server at http://127.0.0.1:8000/
Quit the server with CONTROL-C.
```

Open your browser at http://localhost:8000/

We assure that everything is working properly.

## Structure

By default all new apps in a Django project come with a tests.py file. Any test within this file that starts with \_test\_\_ will be run by Django's test runner. Make sure all test files start with \_test\_\_.

As applications grow in complexity, it's recommended to delete this initial tests.py file and replace it with an app-level **tests** folder that contains individual tests files for each area of functionality.

In real world application you should have something like this :

```shell
├── contact
│   ├── admin.py
│   ├── api.py
│   ├── apps.py
│   ├── __init__.py
│   ├── migrations
│   │   ├── 0001_initial.py
│   │   └── __init__.py
│   ├── models.py
│   ├── serializers.py
│   ├── tests # new
│   │   ├── __init__.py
│   │   ├── test_forms.py # new
│   │   ├── test_models.py # new
│   │   └── test_views.py # new
│   ├── urls.py
│   └── views.py
```

In this article i will keep it simple.

Before writing any tests we will create a new django app and called it **pages** it will contain our static pages(home and about)

```shell
python manage.py startapp pages
```

Register the app here

```python
# Application definition

INSTALLED_APPS = [
   'django.contrib.admin',
   'django.contrib.auth',
   'django.contrib.contenttypes',
   'django.contrib.sessions',
   'django.contrib.messages',
   'django.contrib.staticfiles',
   'rest_framework',  # add this
   'contact'  # add this
   'pages' # new
]
```

Update pages **views.py**

```python
# pages views.py
from django.shortcuts import render
from django.views.generic import TemplateView


class HomePageView(TemplateView):
   template_name = 'pages/home.html'


class AboutPageView(TemplateView):
   template_name = 'pages/about.html'
```

Create **urls.py** inside pages

```shell
touch pages/urls.py
```

```python
# pages/urls.py
from django.urls import path

from .views import HomePageView, AboutPageView

urlpatterns = [
   path('', HomePageView.as_view(), name='home'),
   path('about/', AboutPageView.as_view(), name='about'),
]
```

And finally update your main **urls.py**

```python
from django.contrib import admin
from django.urls import path, include  # add this

urlpatterns = [
   path('admin/', admin.site.urls),
   path("", include("contact.urls")),  # add this
   path("", include("accounts.urls")),  # add this
   path("", include("pages.urls"))  # new
]

```

Create your templates

```shell
mkdir templates/pages
touch templates/pages/home.html
touch templates/pages/about.html
```

Let's add some html content

```html
<!-- templates/pages/home.html -->
<h1>Homepage</h1>

<!-- templates/pages/about.html -->
<h1>About page</h1>
```

Run again your server to assure that everything is working properly.

```shell
python manage.py runserver
Watching for file changes with StatReloader
Performing system checks...

System check identified no issues (0 silenced).
April 22, 2020 - 06:46:31
Django version 3.0.2, using settings 'phonebook.settings'
Starting development server at http://127.0.0.1:8000/
Quit the server with CONTROL-C.
[22/Apr/2020 06:46:33] "GET / HTTP/1.1" 200 2268
```

Open your browser then navigate to [the homepage urls](http://127.0.0.1:8000/)

## Simple TestCase

Now we will test our static pages without involving a database.
Let's test the homepage

```python
# pages/tests.py

from django.http import HttpRequest
from django.test import SimpleTestCase
from django.urls import reverse

from . import views


class HomePageTests(SimpleTestCase):

   def test_home_page_status_code(self):
       response = self.client.get('/')
       self.assertEquals(response.status_code, 200)

   def test_view_url_by_name(self):
       response = self.client.get(reverse('home'))
       self.assertEquals(response.status_code, 200)

   def test_view_uses_correct_template(self):
       response = self.client.get(reverse('home'))
       self.assertEquals(response.status_code, 200)
       self.assertTemplateUsed(response, 'pages/home.html')

   def test_home_page_contains_correct_html(self):
       response = self.client.get('/')
       self.assertContains(response, '<h1>Homepage</h1>')

   def test_home_page_does_not_contain_incorrect_html(self):
       response = self.client.get('/')
       self.assertNotContains(
           response, 'Hi there! No data found.')
```

Now run the tests

```shell
python manage.py test
System check identified no issues (0 silenced).
.....
----------------------------------------------------------------------
Ran 5 tests in 0.015s

OK
```

It's working properly

Let's do the same thing for the about page

```python
# pages/tests.py
class AboutPageTests(SimpleTestCase):

   def test_about_page_status_code(self):
       response = self.client.get('/about/')
       self.assertEquals(response.status_code, 200)

   def test_view_url_by_name(self):
       response = self.client.get(reverse('about'))
       self.assertEquals(response.status_code, 200)

   def test_view_uses_correct_template(self):
       response = self.client.get(reverse('about'))
       self.assertEquals(response.status_code, 200)
       self.assertTemplateUsed(response, 'pages/about.html')

   def test_about_page_contains_correct_html(self):
       response = self.client.get('/about/')
       self.assertContains(response, '<h1>About page</h1>')

   def test_about_page_does_not_contain_incorrect_html(self):
       response = self.client.get('/')
       self.assertNotContains(
           response, 'Hi there! I should not be on the page.')
```

Then run

```shell
python manage.py test

System check identified no issues (0 silenced).
..........
----------------------------------------------------------------------
Ran 10 tests in 0.023s

OK
```

This works fine

## TestCase

**Testcase** is the most common class to use for writing test in Django it gives us the ability to mock queries to the database.

Let's test out our Contact database model

```python
# contact/tests.py
from django.test import TestCase
from django.urls import reverse

from .models import Contact


class ContactTests(TestCase):

   def setUp(self):
       Contact.objects.create(
           first_name='Ousseynou',
           last_name="Diop",
           phone="779929900",
           email="hello@me.com"
       )

   def test_email_content(self):
       contact = Contact.objects.get(id=1)
       expected_object_name = f'{contact.email}'
       self.assertEquals(expected_object_name, 'hello@me.com')

   def test_contact_list_view(self):
       response = self.client.get(reverse('contacts'))
       self.assertEqual(response.status_code, 200)
       self.assertContains(response, 'hello@me.com')
       self.assertTemplateUsed(response, 'contact/contact_list.html')
```

Then run

```shell
python manage.py test

System check identified no issues (0 silenced).
..........
----------------------------------------------------------------------
Ran 10 tests in 0.023s

OK
```

This works fine! let's break the code.

```python
# contact/tests.py
from django.test import TestCase
from django.urls import reverse

from .models import Contact


class ContactTests(TestCase):

   def setUp(self):
       Contact.objects.create(
           first_name='Ousseynou',
           last_name="Diop",
           phone="779929900",
           email="hello@me.com"
       )

   def test_email_content(self):
       contact = Contact.objects.get(id=1)
       expected_object_name = f'{contact.email}'
       self.assertEquals(expected_object_name, 'hello@gmail.com') # new

   def test_contact_list_view(self):
       response = self.client.get(reverse('contacts'))
       self.assertEqual(response.status_code, 200)
       self.assertContains(response, 'hello@me.com')
       self.assertTemplateUsed(response, 'contact/contact_list.html')
```

Then run the test again

```shell
python manage.py test
System check identified no issues (0 silenced).
.F..........
======================================================================
FAIL: test_email_content (contact.tests.ContactTests)
----------------------------------------------------------------------
Traceback (most recent call last):
 File "/home/username/projects/phonebook_rest_api/contact/tests.py", line 20, in test_email_content
   self.assertEquals(expected_object_name, 'hello@gmail.com')
AssertionError: 'hello@me.com' != 'hello@gmail.com'
- hello@me.com
?        ^
+ hello@gmail.com
?       + ^^^


----------------------------------------------------------------------
Ran 12 tests in 0.030s

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

```

Our tests are failed.

Now you understand how tests are important in software engineering.

## Conclusion

In this article we've learned how to write tests for our django project.
The next steps are to write TDD, setup CI/CD in order to maintain code quality.

Thank you for reading 😍 See you in the next article.