To begin with, you need to install the pytest-django plugin so that you can use the Django framework to test a project. Use the command below:
pip install pytest-django
Create a Django Project
Now we need to create a little Django project before we can see how to create unit tests for the project. Don’t worry, I’ll give you all the commands you’ll need to create the project. Don’t forget to create a virtual environment before you start.
If you haven’t already done so, let’s install Django and the test modules:
pip install Django pytest-django
We can now create the Django project and an application that we’ll call library
:
django-admin startproject django_pytest cd django_pytest/
python manage.py startapp library
Next, add the new application to the settings.py
file:
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'library', # <--- line to be added
]
We now need to add code to the project, so we’ll create a model and a view.
Add the following code to the library/models.py
file:
from django.db import models
from django.utils import timezone
class Book(models.Model):
author = models.CharField(max_length=50)
title = models.CharField(max_length=50)
def __str__(self):
return f'{self.author} | {self.title}'
And in the library/views.py
file, add the code that will display the contents of a book using the primary key of the book within the database.
from django.shortcuts import get_object_or_404, render
from .models import Book
def book_infos(request, pk):
book = get_object_or_404(Book, pk=pk)
return render(request, "book_infos.html", {'book':book})
Now you can update the url.py
file for the project:
from django.contrib import admin
from django.urls import path
from library import views # <--- line to be added
urlpatterns = [
path('admin/', admin.site.urls),
path('<int:pk>', views.book_info, name="info") # <--- line to be added
]
Finally, to finish off the application code, you need to create a directory called templates/
within the application directory where you’ll add your HTML file. Then, you’ll be able to create the book_info.html
file in this directory to display book information.
Create the file library/templates/book_info.html
:
<p>{{ book.author }} | {{ book.title }}</p>
Once you’ve finished creating the application, you can migrate the database and launch the application.
The following command creates the migration or applies all new updates made based on the models:
python manage.py makemigrations
Next, you can run the migration using the following command:
python manage.py migrate
If you like, you can launch the application and test that everything’s working correctly:
python manage.py runserver
You’ve finished coding!
Configure Pytest-Django
Before creating your sequence of tests, create a pytest.ini
file in the root directory that contains the manage.py
file. This file contains the location for the project’s settings file (setting.py
). In our case, the setting.py
file is located in the django_pytest
directory.
Add the following to the pytest.ini
file:
[pytest]
DJANGO_SETTINGS_MODULE = django_pytest.settings
You can also add the format of your test files to make it easier to run tests using the pytest command. Add another line with the format you’re using for your project ( tests.py
, test_*.py
or *_tests.py
).
Personally, I like to use the test_*
format, so that’s the one I’m going to add:
python_files = test_*
Launch a Request
The Django framework provides a test client so that you can simulate HTTP requests to our application. You can access the test client by importing and instantiating as follows:
from django.test import Client
client = Client()
This client enables you to simulate your GET
and POST
requests by providing the path and the required data.
Create a GET Request
You can create a GET
request using the following type of instruction:
client = Client()
client.get('/path/', {'key1': 'data1', 'key2': 'data2'})
This is the equivalent of a GET
request using a URL: /path/?key1=data1&key2=data2
.
Create a POST Request
In the same way, you can create a POST
request using the following type of instruction:
client = Client()
client.post('/path/', {'key1': 'data1', 'key2': 'data2'})
This is the equivalent of a POST
request using the URL /path/
and an HTTP header containing key1=data1&key2=data2
.
You now have two get()
and post()
methods, enabling you to run GET
and POST
requests. The first argument must be the path and the second must be a dictionary containing any required data items.
Manage the Database
Before even starting to set up your first unit test for the project, you need to know how to manage the database elements. Don’t forget that you’re creating unit tests, so you mustn’t use the production database and you can’t create a database each time you want to run your tests.
Pytest provides functionality that will set up a temporary database that is built at test run time and then emptied at the end of the testing. You just need to create the required elements in your test database and then run your test scenario.
To do this, you need to specify the @pytest.mark.django_db
decorator before defining your test.
Tests for a Django project can be placed either in a test folder structure at the project root or in each application’s directory.
Without further ado, you’re going to see how to use the following tests.
Test the URLs
First of all, you’re going to create a tests/
folder within the library
application to hold the full set of tests for the application. Then, you can create a test_urls.py
file, which will hold the tests for the urls.py
file.
The test will check that the URL path and view name are correct.
import pytest
from django.urls import reverse, resolve
from library.models import Book
@pytest.mark.django_db
def test_book_info_url():
Book.objects.create(author = "Charles Dickens",
title = "Great Expectations")
path = reverse('info', kwargs={'pk':1})
assert path == "/1"
assert resolve(path).view_name == "info"
Don’t worry, I’m going to explain each line of code to you:
Book.objects.create(author = "Charles Dickens", title = "Great Expectations")
: Create a book using theBook
model.path = reverse('info', kwargs={'pk':1})
: Generate the URL using the name of the view passed as a parameter.assert path == "/1"
: Check if the URL is correct.assert resolve(path).view_name == "info"
: Check if the name of the view is correct and that the URL matches the view name.
Test the Views
Stay in the same test directory and add a test file to check the views (library/tests/test_views.py
).
You’re going to check that the content returned by the view and the HTTP status code are valid. Here’s the code you can use to check this:
import pytest
from django.urls import reverse
from django.test import Client
from library.models import Book
from pytest_django.asserts import assertTemplateUsed
@pytest.mark.django_db
def test_book_info_view():
client = Client()
Book.objects.create(author = "Charles Dickens",
title = "Great Expectations")
path = reverse('info', kwargs={'pk':1})
response = client.get(path)
content = response.content.decode()
expected_content = "Charles Dickens | Great Expectations”
assert content == expected_content
assert response.status_code == 200
assertTemplateUsed(response, "book_info.html")
Right, now for a little explanation of these new lines of code:
client = Client()
: Create a test client. This client will behave like a simplified browser. Note that there’s no need to start the Django server when using this test client.response = client.get(path)
: Request the retrieved URL using thereverse()
function.content = response.content.decode()
: Decode the HTML response returned by the view.expected_content = …
: Specify the expected response.assert content == expected_content
: Check that the view response and the expected response are the same.assert response.status_code == 200
: Check that the HTTP status code is equal to 200.assertTemplateUsed(response, "book_info.html")
: Check that the provided name template was used to generate the response.
Test the Models
You’re going to continue working in the test directory and add a new file to test the application models. In your case, you only have one model to test.
I’ve set up a test that will check the Book
model. We can use the special function __str__
from the Book
class to check this. In our case, it will return information about the instance. We’re going to use the create()
function to help us, because this will return the model instance at the point of insertion into the database.
Copy the code below and test it out:
import pytest
from django.test import Client
from library.models import Book
@pytest.mark.django_db
def test_book_model():
client = Client()
book = Book.objects.create(
author = "Charles Dickens",
title = "Great Expectations")
expected_value = "Charles Dickens | Great Expectations”
assert str(book) == expected_value
Over to You!
It’s time to move on to some serious matters! Your next mission involves our Django project. Watch the video below to learn more about the application:
Because I’m kind, I’m going to give you an initial example of a unit test for this project:
Your Mission:
Write a test plan for this project.
Create a sequence of tests for the whole project using pytest-django.
Find a suggested solution on GitHub!
Let’s Recap!
To configure pytest-django, you need to create a
pytest.ini
file containing the path to the project settings file. You must specify the path using theDJANGO_SETTINGS_MODULE
variable. Otherwise, the tests won’t run.URLs can be tested using the
resolve()
method and theview_name
attribute.All of the views return the data and an HTTP return code that can be tested in a unit test. The return value for the request provides
content
andstatus_code
attributes, which contain the data and the HTTP return code, respectively.Models can be tested using the special method
__str__
, which can be used to send back all information about the model.
We’ve finished looking into the specifics of web applications. Now, we’re going to continue to extend your knowledge of the Pytest framework. I’ll meet you in the next chapter!