A Django view that works on your machine is not guaranteed to work in production — one wrong slug or one missing form field is enough to turn a page into a 500 error. Writing tests with Django’s built-in TestCase and Client lets you call views like a real browser, check the status code, content, and template in just a few lines, then rerun everything with a single python manage.py test before every deploy.

The Problem

Many small Django projects have no tests at all for their views. The only check is opening a browser, clicking through a few pages, and deploying when things look fine. That approach misses edge cases: a slug that does not exist, a form missing a field, a page that requires login. Take a post detail view that calls Post.objects.get() directly:

# views.py
from django.shortcuts import render
from .models import Post
def post_detail(request, slug):
post = Post.objects.get(slug=slug) # wrong slug -> DoesNotExist -> 500 error
return render(request, "blog/detail.html", {"post": post})

A user only needs to mistype one character in the URL to get a 500 error page instead of a 404, and you will only find out when someone reports it. The deeper a bug hides in rare edge cases, the longer it takes to fix.

The Solution

Django already ships django.test.TestCase and a test Client — no extra library needed. TestCase creates a separate test database and cleans it up after each test; the Client sends real requests through the URLconf like a browser and returns a response you can assert on for status code, content, and template. Here is a test for the view above:

# tests.py
from django.test import TestCase
from django.urls import reverse
from .models import Post
class PostDetailViewTests(TestCase):
@classmethod
def setUpTestData(cls):
cls.post = Post.objects.create(
title="Hello Django", slug="hello-django",
content="Post content.",
)
def test_detail_returns_200_and_shows_title(self):
response = self.client.get(
reverse("post-detail", kwargs={"slug": "hello-django"})
)
self.assertEqual(response.status_code, 200)
self.assertContains(response, "Hello Django")
self.assertTemplateUsed(response, "blog/detail.html")
def test_wrong_slug_returns_404(self):
response = self.client.get(
reverse("post-detail", kwargs={"slug": "does-not-exist"})
)
self.assertEqual(response.status_code, 404)

Run python manage.py test — the second test FAILS right away, showing the view returns 500 instead of 404. Fix the view with get_object_or_404 and you are done:

# views.py — fixed version
from django.shortcuts import get_object_or_404, render
from .models import Post
def post_detail(request, slug):
post = get_object_or_404(Post, slug=slug)
return render(request, "blog/detail.html", {"post": post})

Run the tests again and both pass. setUpTestData runs once for the whole class, so tests stay fast; each test still runs inside its own transaction, so data never leaks into another test. When a view changes, rerunning the full suite tells you immediately what broke.

Apply It to Your Project

Start with your three most important views: the detail page most likely to break, the forms that create and edit data, and the pages that require login. Each view only needs two or three tests: the happy path returns 200 and shows the content, the wrong input returns 404 or a redirect, and an invalid form returns the same form with an error message. Add python manage.py test as the last step before every deploy — or into CI — so a new bug never slips into production unnoticed.

Related Posts

Frequently Asked Questions

How is TestCase different from SimpleTestCase?

SimpleTestCase needs no database and is for tests that never touch the DB, such as forms or template tags. TestCase creates its own test database and wraps each test in a transaction, so it fits views that query models.

How is the test Client different from requests?

self.client is Django’s test Client: it goes through the real URLconf, middleware, and views, but needs no running server and no network. It returns a response with status_code, content, context, and templates ready for you to assert on directly.

Do tests slow down deploys?

A small view test suite usually runs in under 10 seconds. Running python manage.py test before a deploy is far cheaper than fixing a 500 error in production in the middle of the night.

Where should tests live?

Put them in each app’s tests.py, or in a tests/ directory for larger apps. Django auto-discovers any file starting with test when you run python manage.py test.

💬 Have you tried this in your own project? If you hit an error or know a shorter way, leave a comment below — I read them all and will add the best ideas to the post.

References