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!