How to Unit Test a Django Form2020-06-15
Django’s test client is really useful for writing integration tests for your project. It’s great because it has a simple API for testing your application similarly to how a web browser would interact with it. Unfortunately it can be slow, because each call creates a request, passes it through all your middleware, view, maybe a template, then the response comes back through the same layers.
The Test Structure chapter of Speed Up Your Django Tests includes a section on rewriting integration tests that use the test client as unit tests. This rewriting makes them faster and more accurate. Here’s one example rewriting some integration tests for a form as unit tests.
Forms are a great example of a component that can be easily unit tested. They accept a dictionary of values, validate it, and return either errors or cleaned data.
For an example, take this form:
It has a few validation steps for the
title field that we’d like to test in isolation.
For reference, here’s the corresponding view:
You can write integration tests for the form with the test client, checking for error messages in the responses’ HTML:
These tests work, but they have two flaws.
First, they have all of that integration test overhead.
To check these error messages, we don’t really care about the details of HTTP or HTML.
But here we have to check HTTP status codes and parse HTML with
assertContains(..., html=True) in every test.
Second, they’re imprecise.
assertContains() calls check for error messages somewhere in the output, rather than directly related to the
If we had two fields with similar validation logic, these tests could accidentally pass because we used bad test data for the other field.
We could rewrite the tests to inspect for a more precise HTML string, but that would couple them further to the details of form rendering.
You can instead test the form directly:
These tests correct the two flaws. They’re faster because they simply pass in and read out dictionaries, with no need to touch anything related to HTTP or HTML. And they’re more precise because they directly inspect the errors for “title,” ignoring the other fields.
Note you’d still want to have some integration tests, to check that the view, form, and template work together:
Given that the form is already fully tested, these view tests are sufficient as they provide full coverage of the only three paths through the view.
I hope this helps you write faster, more targeted tests,
Working on a Django project? Check out my book Speed Up Your Django Tests which covers loads of best practices so you can write faster, more accurate tests.
One summary email a week, no spam, I pinky promise.
- How to Check the Running Django Command
- How to Disallow Auto-named Django Migrations
- Django's Test Case Classes and a Three Times Speed-Up
Tags: django, python
© 2020 All rights reserved.