Test your django Project
Tags: django, testing, python, beginners
Published: 2022-12-31 16:47:35

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](https://www.netflix.com/).

![Django and Netflix](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/g4qht1jfclf5qdz799mo.png)



# 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:

```python
# 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
```

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:
```python
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)
```
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:
```python
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)
```
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: 
```python
# 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
```

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:
```python
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
    )
``` 
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:
```python
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'])
```
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
```python
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')
```
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

```python
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')
```
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: 

```python
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
```

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:
```python
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)
```

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:
```python
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)
```
 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
```shell
python manage.py test
```
The output should looks like this:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/epqpqne6hsv197tg10vo.png)


Congratulations :tada:. 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](https://github.com/fayomihorace/django-netflix-clone)

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