Introduction
In this video, we'll learn the basics of testing in Django while building a simple CRUD application. The form of testing we'll focus on is Test-Driven Development (TDD), where we write tests before writing the actual code.
What is TDD?
TDD might seem strange and redundant at first, but it's a popular approach that ensures your code works better. It also makes it easier to write high-quality code as you have to clearly define the problem before starting to build.
Setting Up the Django Project
First, make sure Django is installed. Then, create a new project named tdd_testing using the command django-admin startproject tdd_testing. Navigate into the project directory and open it in your preferred editor.
Creating the Tasks App
We'll create an app for tasks as we're building a to-do application. In the terminal, run python manage.py startapp task. Then, open the settings.py file and register the task app.
Writing the First Test
Even though we haven't written any code yet, we can start testing. Open the tests.py file in the task app. The first test will check if there's a Django model for tasks and if there are no tasks in the database by default.
Testing the Task Model Existence
Create a test class named TaskModelTest that inherits from TestCase (already imported from Django). Inside the class, write a test function test_task_model_exists. In this function, try to get the count of tasks from the database. Since no tasks should exist yet, assert that the count is zero.
Creating the Task Model
Running the test will result in an error as the task model isn't defined. So, create the Task model in the models.py file. The model will have a title field (a character field with a maximum length of 255) and a description field (a text field that can be blank).
Running Migrations
After creating the model, we need to run migrations to create the corresponding database table. Run python manage.py make migrations and then python manage.py migrate. Now, running the test should pass.
Testing the String Representation of the Task Model
Next, we'll test the string representation of the Task model. We want it to return the title by default instead of the class name. Create a new test function test_model_has_string_representation in the TaskModelTest class. First, create a task in the database, then assert that the string representation of the task is equal to its title.
Fixing the String Representation
The test will fail as the default string representation is the class name. To fix this, add a __str__ method to the Task model in models.py that returns the title. After that, update the test to convert the string representation to a string before comparing.
Testing the Index Page
Now, let's move on to testing the index page. Create a new test class IndexPageTest that inherits from TestCase. Write a test function test_index_page_returns_correct_response to check if the index page returns a 200 status code and uses the correct template (task/index.html).
Creating the Index View and URL
The test will fail as we haven't created the index view yet. In the views.py file, create an index view that renders the task/index.html template. Then, create a new urls.py file in the task app, import the views, and set up the URL patterns to map the root URL to the index view. Import the task.urls into the main urls.py file.
Creating the Index Template
Running the test will now show that the template doesn't exist. Create a templates folder inside the task app, then a task folder inside templates, and finally an index.html template.
Testing if the Index Page Lists Tasks
We'll test if the index page lists out tasks. Create a new test function test_index_page_has_tasks in the IndexPageTest class. First, get the response from the index page, then create a task in the database, and finally assert that the response contains the task's title.
Updating the Index View and Template
The test will fail as the index view and template don't display the tasks. Update the index view to get all tasks from the database and pass them to the template. Then, update the index.html template to loop through the tasks and display their titles.
Testing the Detail Page
Next, we'll test the detail page where we can view the description of a task. Create a new test class DetailPageTest that inherits from TestCase. In the setUp method, create a task with a description.
Testing the Detail Page Response
Write a test function test_detail_page_returns_correct_response to check if the detail page returns a 200 status code and uses the correct template (task/detail.html). Pass the task's ID in the URL when getting the response.
Creating the Detail View and URL
The test will fail as the detail view doesn't exist. Create a detail view in views.py that gets the task from the database based on the primary key in the URL and renders the task/detail.html template. Update the urls.py file in the task app to map the URL with the task ID to the detail view.
Creating the Detail Template
Running the test will show that the template doesn't exist. Create a detail.html template in the task templates folder.
Testing the Content of the Detail Page
We'll test if the detail page shows the correct task title and description. Create a new test function test_detail_page_has_correct_content in the DetailPageTest class. Get the response from the detail page and assert that it contains the task's title and description.
Testing Multiple Tasks on the Detail Page
We also want to make sure that the detail page doesn't show other tasks. In the setUp method of the DetailPageTest class, create a second task. Then, in the test_detail_page_has_correct_content function, assert that the response doesn't contain the title of the second task.
Testing the New Page
Now, let's test the new page where we can create tasks. Create a new test class NewPageTest that inherits from TestCase. Write a test function test_new_page_returns_correct_response to check if the new page returns a 200 status code and uses the correct template (task/new.html).
Creating the New View and URL
The test will fail as the new view doesn't exist. Create a new view in views.py that renders the task/new.html template. Update the urls.py file in the task app to map the /new URL to the new view.
Creating the New Template
Running the test will show that the template doesn't exist. Create a new.html template in the task templates folder.
Testing the Task Form
We'll use forms to create tasks in Django. Create a new forms.py file in the task app, import the necessary modules, and create a NewTaskForm class that inherits from forms.ModelForm. The form will use the Task model and include the title and description fields.
Testing the Form
In the tests.py file, import the NewTaskForm and write tests to check if the form is a subclass of forms.ModelForm, if the title and description fields are in the form's meta, and if the form is valid when provided with data.
Testing if the New Page Renders the Form
Create a new test function test_new_page_form_rendering in the NewPageTest class. Get the response from the new page and assert that it contains the form tag, the CSRF middleware token, and a label for either the title or description field.
Updating the New View and Template
The test will fail as the new view doesn't pass the form to the template and the template doesn't render the form. Update the new view to create an instance of the NewTaskForm and pass it to the template. Then, update the new.html template to display the form.
Testing Invalid Forms
We'll test what happens when an invalid form is submitted. In the test_new_page_form_rendering function, create a new test for an invalid form. Submit a form with an empty title and assert that the response contains the error list and the "This field is required" error message.
Handling Form Submission in the New View
The test will fail as the new view doesn't handle form submission. Update the new view to handle POST requests, create a form instance from the request data, save the form if it's valid, and create an empty form instance if it's invalid.
Testing Form Redirection
We'll test if a valid form submission redirects to the front page. Create a new test function test_valid_form in the NewPageTest class. Submit a valid form, assert that the response redirects to the front page, and that there is one task in the database.
Updating the New View for Redirection
The test will fail as the new view doesn't redirect after a valid form submission. Update the new view to return a redirect to the front page after saving the form.
Testing the Update Page
Next, we'll test the update page where we can update a task. Create a new test class UpdatePageTest that inherits from TestCase. In the setUp method, create a task.
Testing the Update Page Response
Write a test function test_update_page_returns_correct_response to check if the update page returns a 200 status code and uses the correct template (task/update.html). Pass the task's ID in the URL when getting the response.
Creating the Update View and URL
The test will fail as the update view doesn't exist. Create an update view in views.py that gets the task from the database based on the primary key in the URL and renders the task/update.html template. Update the urls.py file in the task app to map the /update/<int:primary_key> URL to the update view.
Creating the Update Template
Running the test will show that the template doesn't exist. Create an update.html template in the task templates folder.
Testing the Update Form
We'll create an UpdateTaskForm in the forms.py file, which is similar to the NewTaskForm. In the tests.py file, write tests to check if the form is valid when provided with data and if it updates the task in the database.
Updating the Update View and Template
The test will fail as the update view doesn't pass the form to the template and the template doesn't render the form. Update the update view to create an instance of the UpdateTaskForm with the task instance, pass it to the template, and handle form submission. Then, update the update.html template to display the form.
Testing the Delete Page
Finally, we'll test the delete page where we can delete a task. Create a new test class DeletePageTest that inherits from TestCase. In the setUp method, create a task.
Testing the Delete Page
Write a test function test_delete_page_actually_delete_a_task to check if the delete page deletes the task, redirects to the front page, and leaves zero tasks in the database.
Creating the Delete View and URL
The test will fail as the delete view doesn't exist. Create a delete view in views.py that deletes the task from the database and redirects to the front page. Update the urls.py file in the task app to map the /delete/<int:primary_key> URL to the delete view.
Conclusion
We've now created tests for a full CRUD application in Django using TDD. If you liked this tutorial, please hit the like button, subscribe to the channel, and click the bell for notifications when new videos are published. See you in the next video!