Test your django Project

fayomihorace

Horace FAYOMI

Posted on December 31, 2022

Test your django Project

One of the best practices in software engineering is to write various kinds of tests after or before we write code, according to the method we use for development like TDD (Test Driven Development). In this tutorial, we will learn the basics of the testing with Django.

This tutorial is the fourth part of a tutorial-course in which you'll learn and understand the basics of Django by building a clone of the popular website Netflix.

Django and Netflix

First, what to test?

Normally, it's important to test all aspects of our code,
that means we do not test any libraries or functionality provided as part of Python or Django. We should trust our framework (Django) and other libraries.

However, in the real world, we can't test all our code most of the time, but we need to test core features and tricky code.
In this course, we will test the following features:

  • movies listing and filtering
  • registration
  • login.

There are several kinds of tests but in this tutorial, we will focus on integration tests (which consist of testint the entire application, all parts together).

Testing movie listing and filtering

Let's create the test class.

  • Open netflix/tests.py file (it has been created by default with the app) and add this code:
# netflix/tests.py
from HTTP import HTTPStatus
from copy import deepcopy

from django.contrib.auth.models import User
from Django.test import TestCase
from django.contrib import auth

from netflix.models import Category, Movie


class IndexTests(TestCase):

    def setUp(self):
        self.category = Category.objects.create(name="Action")
        self.spider_man_movie = Movie.objects.create(
            name="Spider man",
            category=self.category
        )
        self.avatar_movie = Movie.objects.create(
            name="Avatar",
            category=self.category
        )

    def test_index_render_all_movies(self):
        pass

    def test_index_filter_movies(self):
        pass
Enter fullscreen mode Exit fullscreen mode

Explanation:

  • The test class is IndexTests which inherits from django.test.TestCase.
  • It contains 2 tests cases: test_index_render_all_movies to test that index route (/) renders all movies and test_index_filter_movies to test that the filter feature works well.

Note: each test is a function of the class with a name starting with test_.

  • It also contains a method setUp which is a default method defined by Django test framework to set up the testing environment before each test case. It runs before each test case. Inside that method, we have created a category instance as an attribute to IndexTests class. We have also created two movies instances for that category. We have created them because we need to have at least one category and some movies that we can display to test the listing feature. That is why it's called Setup. We should put in it all data and actions required to perform the test.

Note: each test is isolated; that means, to run the test Django will follow these steps:
1- run IndexTests.setUp() to create the testing environment if there is any, and behind the scenes, Django creates a temporary testing database to store any potential model data.
2- run IndexTests.test_index_render_all_movies()
3- clears the temporary database content so the next test will be executed in a new environment.
4- run IndexTests.test_index_filter_movies().
That is why we say that each test case is isolated from others. The changes made in a given test won't affect others and the setup is executed for each test case.

test_index_render_all_movies

Modify the test_index_render_all_movies method like this:

def test_index_render_all_movies(self):
    response = self.client.get("/")
    # make sure index displays the two movies
    self.assertContains(response, self.spider_man_movie.name)
    self.assertContains(response, self.avatar_movie.name)
Enter fullscreen mode Exit fullscreen mode

Explanation:

  • we just simulate a GET request to / (index) with the line response = self.client.get("/")
  • we assert that the response (the index HTML page) contains the spiderman movie name with self.assertContains(response, self.spider_man_movie.name)
  • we do the same for the avatar movie

test_index_filter_movies

Modify the test_index_filter_movies method like this:

def test_index_filter_movies(self):
    # make sure only `Avatar` movie is rendered when the search term is `ava`
    # This also asserts that the search is case insensitive as the real name
    # is `Avatar` with an upper `A` and we search `ava`.
    response = self.client.post(
        "/",
        data={"search_text": "avat"}
    )
    # make sure index displays `Avatar` movie
    self.assertContains(response, self.avatar_movie.name)
    # Make sure index doesn't display `Spider man` movie
    self.assertNotContains(response, self.spider_man_movie.name)
Enter fullscreen mode Exit fullscreen mode

Explanation:

  • We simulate the POST on the search form with the search text "avat". Because there is only avatar movie whose names contain avat in a case insensitive, then it should be the only movie displayed.
  • we then assert that the response contains Avatar movie but not spider man movie. That means our filter works well.

Testing registration

  • Create the test class: Always inside netflix/forms.py file add the following, right after IndexTests class:
# netflix/forms.py

TEST_DATA = {
    "firstname": "John",
    "lastname": "Joe",
    "email": "johnjoe@gmail.com",
    "password": "NetflixClone2022",
    "password_conf": "NetflixClone2022"
}
...
...
class RegisterTests(TestCase):

    def test_get_registration_page(self):
        pass

    def test_registration_with_valid_data(self):
        pass

    def test_registration_with_empty_fields(self):
        pass

    def test_registration_with_wrong_password_confirmation(self):
        pass
Enter fullscreen mode Exit fullscreen mode

Note: TEST_DATA is a dictionary that contains good registration information.
Also, we don't need a setup method which is why it's not present.

test_get_registration_page

Here, we test that user that will retrieve the registration page. Add this code:

def test_get_registration_page(self):
    # We call the `register` route using `GET`
    response = self.client.get("/register")
    # We assert that no error is returned
    self.assertEqual(response.status_code, HTTPStatus.OK)
    # We assert that the returned page contains the button for registration
    # form
    self.assertContains(
        response,
        '<button type="submit">Register</button>',
        html=True
    )
Enter fullscreen mode Exit fullscreen mode

Explanation: We call the registration page using GET on /register route and we make sure the response (HTML) contains the register button.

test_registration_with_valid_data

Here, We make sure that we can register with valid data. Add this code:

def test_registration_with_valid_data(self):
    # We make sure that no user exists in the database before the registration
    self.assertEqual(User.objects.count(), 0)

    # We call the `register` route using `POST` to simulate form submission
    # with the good data in the setup
    self.client.post("/register", data=TEST_DATA)

    # We make sure that there is 1 user created in the database after the registration
    self.assertEqual(User.objects.count(), 1)

    # We make sure that newly created user data are the same as we used during registration
    new_user = User.objects.first()
    self.assertEqual(new_user.first_name, TEST_DATA['firstname'])
    self.assertEqual(new_user.last_name, TEST_DATA['lastname'])
    self.assertEqual(new_user.email, TEST_DATA['email'])
Enter fullscreen mode Exit fullscreen mode

Explanation: We first make sure that no user exists in the database at the beginning. Secondly, we simulate the registration form POST with some valid data. Then we make sure that a user instance is created with the data used during the registration form submission.

test_registration_with_empty_fields

def test_registration_with_empty_fields(self):
    # We make sure that no user exists in the database before the registration
    self.assertEqual(User.objects.count(), 0)

    # We call the `register` route using `POST` to simulate form submission
    # with empty fields data
    response = self.client.post(
        "/register",
        data={
            "firstname": "",
            "lastname": "",
            "email": "",
            "password": "",
            "password_conf": ""
        }
    )

    # We make sure that there no user in the database after the registration
    # failure. That means no user has been created
    self.assertEqual(User.objects.count(), 0)
    # Make sure the required message is displayed
    self.assertContains(response, 'This field is required')
Enter fullscreen mode Exit fullscreen mode

Explanation: We try to submit the registration form with empty fields and we assert that the response fails and that no user is created.

test_registration_with_wrong_password_confirmation

def test_registration_with_wrong_password_confirmation(self):
    # We make sure that there is no user in the database before the registration
    self.assertEqual(User.objects.count(), 0)

    # We call the `register` route using `POST` to simulate form submission
    # with wrong password confirmation good data in the setup
    # This time, to create the invalid dict, we create a copy of the good
    # data of the setup first.
    bad_data = deepcopy(TEST_DATA)
    bad_data['password_conf'] = "Wrong Password Confirmation"
    response = self.client.post(
        "/register",
        data=bad_data
    )

    # We make sure that there is no user in the database after the registration
    # failure. That means no user has been created
    self.assertEqual(User.objects.count(), 0)

    # Make sure the wrong confirmation is displayed
    self.assertContains(response, 'wrong confirmation')
Enter fullscreen mode Exit fullscreen mode

Explanation: We try to submit the registration form with a password confirmation value different that the password value. We then make sure an error is raised and no user is created.

Testing login

  • Create the test class: Always inside netflix/forms.py file adds the following, right after RegisterTests class:
class LoginTests(TestCase):

    def setUp(self):
        self.user = User.objects.create(username="johnjoe@gmail.com")
        self.user_password = "NetflixPassword2022"
        self.user.set_password(self.user_password)
        self.user.save()

    def test_login_with_invalid_credentials(self):
        pass

    def test_login_with_good_credentials(self):
        pass
Enter fullscreen mode Exit fullscreen mode

Explanation:

  • We have added a setup because, as we want to just test the login, we need some existing users. Thus we have created a user and set a password

test_login_with_invalid_credentials

Add the following code:

def test_login_with_invalid_credentials(self):
    self.assertFalse(auth.get_user(self.client).is_authenticated)
    response = self.client.post(
        "/login",
        data={"email": self.user.username, "password": "Wrong password"}
    )
    self.assertContains(response, 'Invalid credentials.')
    self.assertFalse(auth.get_user(self.client).is_authenticated)
Enter fullscreen mode Exit fullscreen mode

Explanation:

  • We make sure that initially the user was not authenticated with self.assertFalse(auth.get_user(self.client).is_authenticated)
  • We try to POST the login form using the wrong password and then make sure that the response contains the error Invalid credentials.
  • We assert that the user is still not authenticated.

test_login_with_invalid_credentials

Add the following code:

def test_login_with_good_credentials(self):
    self.assertFalse(auth.get_user(self.client).is_authenticated)
    self.client.post(
        "/login",
        data={"email": self.user.username, "password": self.user_password}
    )
    self.assertTrue(auth.get_user(self.client).is_authenticated)
Enter fullscreen mode Exit fullscreen mode

Explanation:

  • We make sure that initially the user was not authenticated with self.assertFalse(auth.get_user(self.client).is_authenticated)
  • We try to POST the login form using a good password.
  • We assert that the user is this time authenticated.

Finally run the test

Just run on the project shell

python manage.py test
Enter fullscreen mode Exit fullscreen mode

The output should looks like this:

Image description

Congratulations 🎉. We have tested the main features of our Django project.

Also, remember that the final source code is available here:
https://github.com/fayomihorace/django-netflix-clone

In the next part, we will learn how to deploy your Django application.

💖 💪 🙅 🚩
fayomihorace
Horace FAYOMI

Posted on December 31, 2022

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related

Test your django Project
django Test your django Project

December 31, 2022