Testing Django Applications

Introduction

Automated tests protect your blog from regressions—broken URLs, invalid forms, permission bugs. Django ships django.test.TestCase, a test Client that simulates HTTP, and a test database that rolls back after each test. This chapter tests models, views, authentication, and forms—the same confidence Flask pytest provides for microframework apps.

Prerequisites

Run the Test Suite

bash
python manage.py test
python manage.py test blog
python manage.py test blog.tests.test_views.PostListTests.test_list_status

Verbose:

bash
python manage.py test -v 2

Django creates a test database (often test_<dbname>), runs tests inside transactions, and destroys it when finished.

Project Layout

text
blog/
├── tests/
│   ├── __init__.py
│   ├── test_models.py
│   ├── test_views.py
│   └── test_forms.py

Or single blog/tests.py for small apps—split files as tests grow.

Model Tests

blog/tests/test_models.py:

Code explanation:

  • TestCase wraps each test in a transaction and rolls back
  • setUp runs before every test method
  • Use assertRaises for integrity errors (SQLite/MySQL behavior may vary—IntegrityError is precise)

View Tests With Client

blog/tests/test_views.py:

Code explanation:

  • reverse("post-list") uses named URLs — routing chapter
  • assertContains checks rendered HTML

Authentication Tests

client.login() sets session—no need to POST login form in every test.

Form Validation Tests

blog/tests/test_forms.py:

Test forms without HTTP when validation logic is complex.

Fixtures and setUpTestData

For expensive setup shared across tests in a class:

python
class PostListTests(TestCase):
    @classmethod
    def setUpTestData(cls):
        cls.user = User.objects.create_user(username="dave", password="pass12345")
        cls.post = Post.objects.create(
            title="Shared",
            slug="shared",
            body="Body",
            author=cls.user,
            published=True,
        )

setUpTestData runs once per class—faster than recreating rows every test.

Load JSON fixtures:

bash
python manage.py loaddata blog/fixtures/posts.json

Use sparingly in tests—prefer setUp factories for clarity.

pytest-django (Optional)

Many teams use pytest:

bash
pip install pytest pytest-django

pytest.ini:

ini
[pytest]
DJANGO_SETTINGS_MODULE = config.settings.development
python_files = tests.py test_*.py

blog/tests/test_views_pytest.py:

python
import pytest
 
@pytest.mark.django_db
def test_post_list(client, django_user_model):
    user = django_user_model.objects.create_user("eve", password="pass12345")
    ...

Same assertions—pytest style is preference, not required for this track.

CI Hook (Preview)

Run tests in GitHub Actions — full example in Docker and CI:

yaml
- run: python manage.py test

Post-Chapter Checklist

  • python manage.py test passes for your app
  • Model __str__ and constraints tested
  • Views tested for status codes and content
  • Login-required views tested with client.login
  • At least one form validation test

FAQ

Tests use wrong database?

Check DATABASES in test settings—Django clones default DB.

CSRF failures in tests?

Client handles CSRF automatically on POST when using follow=True or enforce_csrf_checks=False on client (advanced).

Speed up suite?

--parallel, setUpTestData, mock slow external APIs.

factory_boy?

Generates fake User / Post rows—nice for large suites.

Test migrations?

MigrationTestCase or migrate in integration tests—usually smoke-test migrate in CI instead.