Lesson 21 / 26
Testing Django Apps
TestCase, the test client and query counts.
Fast, isolated database tests
Django's TestCase wraps each test in a transaction that is rolled back, with setUpTestData creating shared fixtures once per class. The built-in test client performs requests against your URLs without a server, and assertNumQueries guards performance. manage.py test creates a separate test database automatically. Many teams use pytest with pytest-django for fixtures and nicer output.
Verified and hardened
Both frameworks make testing straightforward; Django adds strong security defaults and checks.
The catalog tests
I ran this with Django 6.1.1 and Python 3.12 in a demo project (shopsite with a catalog app) using SQLite. Three tests: filtering by price and stock, a 404 for a missing product, and a query-count guard.
from decimal import Decimal
from django.test import TestCase
from django.urls import reverse
from .models import Category, Product
class ProductApiTests(TestCase):
@classmethod
def setUpTestData(cls):
office = Category.objects.create(name="Office")
Product.objects.create(category=office, name="Pen", price=Decimal("899"))
Product.objects.create(category=office, name="Notebook", price=Decimal("120"))
Product.objects.create(category=office, name="Lamp", price=Decimal("1499"), in_stock=False)
def test_list_filters_by_price_and_stock(self):
r = self.client.get(reverse("product-list"), {"max_price": "1000"})
self.assertEqual(r.status_code, 200)
self.assertEqual([p["name"] for p in r.json()["results"]], ["Notebook", "Pen"])
def test_missing_product_is_404(self):
self.assertEqual(self.client.get(reverse("product-detail", args=[999])).status_code, 404)
def test_list_uses_two_queries_or_less(self):
with self.assertNumQueries(1):
self.client.get(reverse("product-list"))Running the tests, run
I ran this with Django 6.1.1 and Python 3.12 in a demo project (shopsite with a catalog app) using SQLite. All three tests pass (timing replaced with (time)); the list view needs exactly one query thanks to select_related.
python manage.py test catalog -v 2
Output:
test_list_filters_by_price_and_stock (catalog.tests.ProductApiTests.test_list_filters_by_price_and_stock) ... ok test_list_uses_two_queries_or_less (catalog.tests.ProductApiTests.test_list_uses_two_queries_or_less) ... ok test_missing_product_is_404 (catalog.tests.ProductApiTests.test_missing_product_is_404) ... ok Ran 3 tests in (time) OK
Quick check: What does Django's TestCase do after each test?
- Deletes the database file permanently
- Rolls back the database changes
- Commits changes to production
- Restarts Python
Answer
Rolls back the database changes — Isolated tests.