Hello! Welcome back.
In our last lesson, we laid the groundwork for testing by focusing on the "M" in Django's MVT architecture: the models. You learned how to use django.test.TestCase to verify your model's field attributes and custom methods, ensuring your data layer is solid.
Today, we move up the stack to test the "V": the views. Views are the bridge between your data and the user, handling incoming requests and crafting responses. Testing them is crucial to ensure your application behaves correctly from a user's perspective.
By the end of this lesson, you will be able to simulate user interactions like visiting a page, submitting a form, or deleting an item, and verify that your views respond exactly as you expect. This involves:
- Using Django's test
clientto make simulated HTTP requests (GET, POST, etc.). - Asserting response status codes (e.g., 200 OK, 302 Redirect, 404 Not Found).
- Verifying that the correct template is rendered.
- Checking the data passed to the template via the context.
- Confirming that database changes occur when they should.
Let's begin.
1. The Django Test Client: Your Automated Browser
In the previous lesson, we used django.test.TestCase, which provides an isolated database for each test run. This class also comes with a powerful tool attached to it: self.client.
You can think of self.client as a dummy web browser that you control with Python code. It allows you to "visit" URLs in your application, submit data, and inspect the response, all without needing to run a development server or open a real browser.

The basic workflow for testing a view is:
- Arrange: Set up any necessary database state (e.g., create a
Postobject to be displayed). - Act: Use
self.clientto make a request to a specific URL. - Assert: Check that the response from the view is correct.
Testing tools | Django documentation
The official Django documentation provides the definitive guide to the test client. Let's review the key sections that introduce the client and its capabilities.
Please read the sections 'The test client' and 'Default test client'. These sections explain what the client is, what it's used for, and how it's automatically available as self.client in your test cases.
2. Testing Simple GET Requests
The most common type of view test is for a simple GET request, such as loading a list or detail page. Let's imagine we have a view that lists all blog posts. Our test should verify three main things:
- Does the page load successfully (Status Code 200)?
- Does it use the correct template?
- Does it contain the data we expect?
Mastering Django Unit Testing: An In-Depth Introduction and Tutorial
This video provides a clear, practical demonstration of testing a simple GET request for an index page. Pay close attention to the assertions used.
Watch the segment from 18:50 to 21:32. The author tests an index view, asserting the status code is 200 and that the correct template is used via assertTemplateUsed.
Example: Testing a Post List View
Let's apply this to a PostListView. Assume we have a blog app with a URL named 'post_list'.
# blog/tests/test_views.py
from django.test import TestCase
from django.urls import reverse
from django.contrib.auth.models import User
from ..models import Post
class PostViewsTest(TestCase):
@classmethod
def setUpTestData(cls):
# Create a user and a few posts
user = User.objects.create_user(username='testuser', password='password')
number_of_posts = 5
for post_id in range(number_of_posts):
Post.objects.create(
title=f'Post {post_id}',
content=f'Content for post {post_id}.',
author=user,
)
def test_post_list_view_at_correct_location(self):
# Get the response from the client
response = self.client.get('/blog/')
self.assertEqual(response.status_code, 200)
def test_post_list_view_accessible_by_name(self):
# Using reverse() is best practice as it's not hardcoded
response = self.client.get(reverse('blog:post_list'))
self.assertEqual(response.status_code, 200)
def test_post_list_view_uses_correct_template(self):
response = self.client.get(reverse('blog:post_list'))
self.assertEqual(response.status_code, 200)
# Check that the expected template was used to render the response
self.assertTemplateUsed(response, 'blog/post_list.html')
def test_post_list_view_pagination(self):
# Assuming paginate_by = 3 in the view
response = self.client.get(reverse('blog:post_list'))
self.assertEqual(response.status_code, 200)
# The view passes a context variable 'is_paginated'
self.assertTrue(response.context['is_paginated'])
# Check the number of posts on the first page
self.assertEqual(len(response.context['post_list']), 3)
def test_post_list_view_shows_all_posts_on_second_page(self):
# Request the second page
response = self.client.get(reverse('blog:post_list') + '?page=2')
self.assertEqual(response.status_code, 200)
self.assertTrue(response.context['is_paginated'])
# Check the number of posts on the second page (5 total - 3 on first page)
self.assertEqual(len(response.context['post_list']), 2)
Notice how we can inspect response.context. This dictionary contains all the data passed from the view to the template, which is incredibly useful for verifying the view's logic.
3. Testing Authenticated Views
Many views should only be accessible to logged-in users. Django's test client provides helpers to manage authentication state.
You need to test two scenarios:
- An unauthenticated user is redirected to the login page.
- An authenticated user can access the page successfully.
Django Tutorial Part 10: Testing a Django web application
The MDN tutorial has an excellent section on testing views that are restricted to logged-in users. It demonstrates how to log a user in and test for redirects.
Read the section 'Views that are restricted to logged in users'. Focus on the test_redirect_if_not_logged_in and test_logged_in_uses_correct_template methods. Note the use of self.client.login() and self.assertRedirects().
Example: Testing a "My Posts" View
Let's say we have a view at /blog/my-posts/ (URL name 'my_posts') that is protected by LoginRequiredMixin.
# In your test class...
def test_my_posts_view_redirects_if_not_logged_in(self):
response = self.client.get(reverse('blog:my_posts'))
# Assert that the status code is 302 (redirect)
self.assertEqual(response.status_code, 302)
# You can also use assertRedirects for a more robust check
self.assertRedirects(response, '/accounts/login/?next=/blog/my-posts/')
def test_my_posts_view_for_logged_in_user(self):
# Log the user in
self.client.login(username='testuser', password='password')
# Now, make the request as a logged-in user
response = self.client.get(reverse('blog:my_posts'))
# Check that the view is now accessible
self.assertEqual(response.status_code, 200)
self.assertTemplateUsed(response, 'blog/my_posts.html')
# You could also check that the context only contains posts by 'testuser'
4. Testing Views that Change Data (POST, DELETE)
Testing views that handle form submissions (POST) or deletions (DELETE) follows a similar pattern, but with an important addition: you must verify that the database state has changed correctly.
Django Testing Tutorial - Testing Views #3
This video walks through testing create, delete, and update operations, which map directly to POST and DELETE requests.
Watch the segments on testing a POST request (06:26 - 09:28) and a DELETE request (10:11 - 12:14). Observe how data is sent with the request and how the tests assert both the response and the change in the database (e.g., a new expense is added, or the count becomes zero after deletion).
Example: Testing Post Creation and Deletion
Let's assume we have a view to create a new post (POST to /blog/new/) and a view to delete one (POST or DELETE to /blog/1/delete/).
# In your test class...
def test_create_post_view(self):
self.client.login(username='testuser', password='password')
initial_post_count = Post.objects.count()
# The data for the new post
post_data = {
'title': 'A New Post',
'content': 'This is the content.'
}
# Make a POST request to the 'post_create' URL
response = self.client.post(reverse('blog:post_create'), data=post_data)
# On success, a create view usually redirects
self.assertEqual(response.status_code, 302)
# Check that a new post was created
self.assertEqual(Post.objects.count(), initial_post_count + 1)
# Check that the new post has the correct title
last_post = Post.objects.last()
self.assertEqual(last_post.title, 'A New Post')
def test_delete_post_view(self):
self.client.login(username='testuser', password='password')
post_to_delete = Post.objects.get(id=1)
initial_post_count = Post.objects.count()
# Make a POST request to the 'post_delete' URL
response = self.client.post(reverse('blog:post_delete', args=[post_to_delete.id]))
# Should redirect after deletion
self.assertEqual(response.status_code, 302)
# Check that the post was deleted
self.assertEqual(Post.objects.count(), initial_post_count - 1)
Test your understanding!
Imagine you have a CreateView for adding a Product. A POST request with an empty name field should not create a product and should re-render the form page with an error.
How would you test this scenario? What status code would you expect, and what else would you check?
Show answer
You would expect a status code of 200, not a redirect. A failed form validation typically re-renders the same page with the form, including error messages.
Here's how you could test it:
def test_create_product_with_invalid_data(self):
self.client.login(username='testuser', password='password')
initial_product_count = Product.objects.count()
# POST request with invalid data (empty name)
response = self.client.post(reverse('product_create'), data={'name': '', 'price': '19.99'})
# 1. Assert the page re-renders successfully (status 200)
self.assertEqual(response.status_code, 200)
# 2. Assert no new product was created in the database
self.assertEqual(Product.objects.count(), initial_product_count)
# 3. (Advanced) Assert that the form in the context contains an error for the 'name' field
self.assertFormError(response, 'form', 'name', 'This field is required.')
The assertFormError is a specific and very useful assertion for testing form validation.
Conclusion
In this lesson, you've learned how to thoroughly test your Django views, a critical skill for building robust web applications. By simulating user requests, you can automatically verify that your application's logic is correct from end to end.
Key Takeaways:
self.clientis your automated browser: Use it withinTestCaseto simulateGET,POST, and other HTTP requests.- A view test checks the entire response: This includes the status code, the template used, the context data, and any redirects.
- Test for all user states: Always check how a view behaves for both authenticated and unauthenticated users if it's protected.
- Verify database side-effects: For views that create, update, or delete data, always assert that the database was modified correctly.
Next Up
You've now tested both the "M" and "V" of Django's server-rendered architecture. As per your goal to learn backend development, our next set of lessons will pivot to building and testing REST APIs with the Django REST Framework (DRF). In the very next lesson, we will start by installing DRF and learning about its core component: Serializers.