Django testing using pytest
Tags: django, tests, pytest, python
Published: 2020-05-02 19:05:30

---
title: "Django testing using pytest"
published: true
description: "Django testing using pytest"
tags: django, tests, pytest, python
cover_image: https://images.unsplash.com/photo-1518349619113-03114f06ac3a?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=750&q=80
---

Originally posted on [my blog](https://rapidpython.com/django-testing-using-pytest/)

## Introduction

In our previous [article](https://ousseynoudiop.com/django-testing-best-pratices-and-examples/) we've discussed writing simple tests in Django.
In this article, we'll go one step further.

**Pytest** helps you write better programs.

The [Pytest](https://docs.pytest.org/en/latest/index.html#pytest-helps-you-write-better-programs) framework makes it easy to write small tests, yet scales to support complex functional testing for applications and libraries.

Here is an example of a simple test

```python
def inc(x):
   return x + 1


def test_answer():
   assert inc(3) == 5
```

Execute it, what is the result?

A great developer should test his code before the client or user interaction.
Testing is not an easy task as you may think.
Writing advanced tests is very challenging but pytest makes it a lot easier than before.

## Why Pytest

You can test your Django application without using a Library but **pytest** offers some features that are not present in Django’s standard test mechanism: :

- Detailed info on failing assert statements (no need to remember self.assert\* names);
- Auto-discovery of test modules and functions;
- Modular fixtures for managing small or parametrized long-lived test resources;
- Can run unit test (including trial) and nose test suites out of the box;
- More about pytest [here](https://docs.pytest.org/en/latest/index.html#pytest-helps-you-write-better-programs)

## Pytest vs Django unit testing Framework

Here is a basci comparaison

```python
from django.test import TestCase


class TestHelloWorld(TestCase):
   def test_hello_world(self):
       self.assertEqual("hello world", "hello world")
```

Using **Pytest**

```python
def test_hello_world():
   assert "hello_world" == "hello_world"
```

## Setting Up Pytest Django

[pytest-django](https://pytest-django.readthedocs.io/en/latest/) is a plugin for pytest that provides a set of useful tools for testing Django applications and projects.

You can find the final code [here](#)

Create a new virtual environment

```shell
mkdir django_testing_using_pytest && cd django_testing_using_pytest
virtualenv venv # this command will create a virtual environment called venv
```

Activate our virtual environment

```shell
source /venv/bin/activate
```

Install Django and pytest-django

```shell
pip install django pytest-django
```

Create a new Django project from the terminal

```shell
django-admin startproject django_testing .
```

Don't forget to put the dot(.) at the end.

Create a simple **django** application

```shell
python manage.py startapp main_app
```

Register our newly created app

```python
# settings.py
INSTALLED_APPS = [
  'django.contrib.admin',
  'django.contrib.auth',
  'django.contrib.contenttypes',
  'django.contrib.sessions',
  'django.contrib.messages',
  'django.contrib.staticfiles',
  'main_app.apps.MainAppConfig' # add this
]
```

Change the templates directory

```python
# settings.py
TEMPLATES = [
  {
      'BACKEND': 'django.template.backends.django.DjangoTemplates',
      'DIRS': [os.path.join(BASE_DIR, 'templates')],  # add this
      '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',
          ],
      },
  },
]
```

Run the application and make sure everything is working as expected

```shell
python manage.py runserver
```

```shell
Watching for file changes with StatReloader
Performing system checks...

System check identified no issues (0 silenced).

You have 17 unapplied migration(s). Your project may not work properly until you apply the migrations for app(s): admin, auth, contenttypes, sessions.
Run 'python manage.py migrate' to apply them.

May 01, 2020 - 20:28:17
Django version 3.0.5, using settings 'django_testing.settings'
Starting development server at http://127.0.0.1:8000/
Quit the server with CONTROL-C.
```

Make sure **DJANGO_SETTINGS_MODULE** is defined.

Create a file called pytest.ini in your project root directory that contains:

```shell
touch pytest.ini
```

```ini
# pytest.ini
[pytest]
DJANGO_SETTINGS_MODULE = django_testing.settings
# -- recommended but optional:
python_files = tests.py test_*.py *_tests.py
```

Let's run our test suite

```shell
$ pytest
============================= test session starts ==============================
platform linux -- Python 3.7.5, pytest-5.4.1, py-1.8.1, pluggy-0.13.1
rootdir: /home/username/projects/username/source-code/django_testing_using_pytest, inifile: pytest.ini
plugins: django-3.9.0
collected 0 items
```

You may ask why run test suite instead of Django **manage.py** command, here is the answer :

- Less boilerplate: no need to import unittest, create a subclass with methods. Just write tests as regular functions.
- Manage test dependencies with fixtures.
- Run tests in multiple processes for increased speed.
- There are a lot of other nice plugins available for pytest.
- Easy switching: Existing unittest-style tests will still work without any modifications.

See the [pytest](http://docs.pytest.org/) documentation for more information on pytest.

## Test Discovery

The first thing that **pytest** provides is test discovery. Like nose, starting from the directory where it is run, it will find any Python module prefixed with test* and will attempt to run any defined unittest or function prefixed with test*. **pytest** explores properly defined Python packages, searching recursively through directories that include **init**.py modules.

Here is an example :

```shell
├── django_testing
│   ├── asgi.py
│   ├── __init__.py
│   ├── settings.py
│   ├── urls.py
│   └── wsgi.py
├── main_app
│   ├── admin.py
│   ├── apps.py
│   ├── __init__.py
│   ├── migrations
│   │   ├── __init__.py
│   │   └── __pycache__
│   │       └── __init__.cpython-37.pyc
│   ├── models.py
│   ├── test_contact.py # checked for tests
│   ├── test_blog.py # checked for tests
│   ├── test_todo.py # checked for tests
│   └── views.py
```

Let's write a test for our model

```python
# models.py
from django.db import models

class Contact(models.Model):
   first_name = models.CharField(max_length=150)
   last_name = models.CharField(max_length=150)
   phone = models.CharField(max_length=150)
   email = models.CharField(max_length=150, blank=True)
   created_at = models.DateTimeField(auto_now_add=True)

   def __str__(self):
       return self.phone
```

```python
# tests.py

import pytest
from .models import Contact


@pytest.mark.django_db
def test_contact_create():
   contact = Contact.objects.create(
       first_name="John",
       last_name="Doe",
       email="john@gmail.com",
       phone="00221 70 992 33 43"
   )
   assert contact.email == "john@gmail.com"
   assert contact.phone == "00221 70 992 33 43"
```

Run the test

```shell
pytest
============================= test session starts =============================
platform linux -- Python 3.7.5, pytest-5.4.1, py-1.8.1, pluggy-0.13.1
django: settings: django_testing.settings (from ini)
rootdir: /home/username/projects/username/source-code/django_testing_using_pytest, inifile: pytest.ini
plugins: django-3.9.0
collected 1 item

main_app/tests.py .                                                     [100%]

============================== 1 passed in 0.27s ==============================
```

Our test passed.

Let's break our code and run the test again

```python
# tests.py

import pytest
from .models import Contact


@pytest.mark.django_db
def test_contact_create():
   contact = Contact.objects.create(
       first_name="John",
       last_name="Doe",
       email="john@gmail.com",
       phone="00221 70 992 33 43"
   )
   assert contact.email == "john@hotmail.com"
   assert contact.phone == "00221 70 992 33 43"
```

Run the test

```shell
================================== FAILURES ===================================
_____________________________ test_contact_create _____________________________

   @pytest.mark.django_db
   def test_contact_create():
       contact = Contact.objects.create(
           first_name="John",
           last_name="Doe",
           email="john@gmail.com",
           phone="00221 70 992 33 43"
       )
>       assert contact.email == "john@hotmail.com"
E       AssertionError: assert 'john@gmail.com' == 'john@hotmail.com'
E         - john@hotmail.com
E         ?      ^^^
E         + john@gmail.com
E         ?      ^

main_app/tests.py:14: AssertionError
=========================== short test summary info ===========================
FAILED main_app/tests.py::test_contact_create - AssertionError: assert 'john...
============================== 1 failed in 0.33s ==============================
```

## Conclusion

In this article we've how to setup Pytest with Django, you can do more stuff with **Pytest** it's well documented.
We'll write test coverage in our next article.

Thanks for reading, See you next.