A blueprint for django tests

When it comes to testing Django, the easiest part to test are your views as this is the entry point to most of the custom business logic that isn't covered by Django itself.

The structure of a view test ought to follow this relatively simple pattern:

  1. Setup the required data
  2. Assert the initial state of the system
  3. Construct the data to be sent to the view (if required)
  4. Make a call to the view via the Django client
  5. Assert the response is as expected (HTTP status code, any content)
  6. Assert the final state of the system as a result of the view logic.

Some important notes about this pattern:

  • The complexity that is hidden in the in the setup of the data. This depends hugely on the complexity of the data model.
  • The assertions before making any changes are key to demonstrate that it is a single test making the expected changes and not some other test making the changes. Tests need to work in isolation from each other.
  • Tests should be DRY - DO Repeat Yourself, it is fine to copy/paste a test then change a single line to cover a different case.
  • Related to the last point keep the tests simple and obvious, don't try to do fancy logic by deriving data from model data, just type it out.
  • Finally don't use Django fixtures, but generate the models dynamically. Other options here include pytest fixtures or factory_boy

Finallly a quick example of some pseudo code:


class MyTestCase(TestCase):

  def my_view_test(self):
    # Step 1
    user = User.objects.create(...)
    profile = Profile.objects.create(user=user, ...)
    # Step 2
    self.assertEqual(profile.age, None)
    self.assertEqual(profile.twitter, '')

    # Step 3
    data = {
      'age': 23,
      'bio': 'I like to build things'
      'twitter': 'nanorepublica'
    }

    # Step 4
    url = reverse('profile-update')
    resp = self.client.post(url, data)

    # Step 5
    self.assertRedirects(resp, '/profile')

    # Step 6
    self.assertEqual(profile.age, 23)
    self.assertEqual(profile.twitter, 'nanorepublica')