James CookeJames Cooke

Django Contexts and get

Background

If you know me, then you know that I’m an avid tester. It could even be argued that I test too extensively as part of my day-to-day development, but that’s a post for another day.

On a recent project, a particular view started failing with the error:

AttributeError: 'ContextList' object has no attribute 'get'

I wasn’t happy with just changing the tests to work again, so I dug down into why they started failing.

TL;DR

To get a value from a Context object returned by the Django Test Client, then it’s better to use the [] operator than the get method.

For example:

# In a test, after doing
response = self.client.get(reverse('home'))

# ... then it's better to use [] to test the context
self.assertEqual(response.context['name'], 'Homer')

# ...than to use get
self.assertEqual(response.context.get('name'), 'Homer')

Reason

It turns out that the problem was to do with a developer on the project changing how the template for the view was generated. They changed a view that was using a single template, to a couple of templates using Django’s template inheritance and the extends template tag.

This then effects how Django’s test client returns the Context object for inspection.

To test this I prepared the following test:

from django.core.urlresolvers import reverse
from django.test import TestCase


class Tests(TestCase):

    def test_get(self):
        response = self.client.get(reverse('home'))
        self.assertEqual(response.context.get('name'), 'Homer')

    def test_operator(self):
        response = self.client.get(reverse('home'))
        self.assertEqual(response.context['name'], 'Homer')

The home view was just a simple template renderer:

from django.shortcuts import render


def home(request):
    return render(request, 'home.html', {'name': 'Homer', })

Simple works

When the ‘home.html’ template is a simple template with no inheritance (it can be completely empty), then both tests pass.

home.html’ template code:

<p>Hello World</p>

Test run:

./manage.py test
Creating test database for alias 'default'...
..
----------------------------------------------------------------------
Ran 2 tests in 0.027s

OK
Destroying test database for alias 'default'...

Template inheritance fails with get

Now adjust ‘home.html’ to extend another template ‘base.html’ which has arbitrary contents.

New ‘home.html’ template code:

{% extends 'base.html' %}
<p>Hello World</p>

Test run:

./manage.py test
Creating test database for alias 'default'...
E.
======================================================================
ERROR: test_get (mini.tests.Tests)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/home/james/active/mini/mmm/mini/tests.py", line 9, in test_get
      self.assertEqual(response.context.get('name'), 'Homer')
AttributeError: 'ContextList' object has no attribute 'get'

----------------------------------------------------------------------
Ran 2 tests in 0.029s

FAILED (errors=1)
Destroying test database for alias 'default'...

So the test_get case, which was using get failed.

Conclusion

It’s definitely more robust to be using list access [] on Context objects returned by the Django Test Client where possible when checking values passed through to templating.

Grab me on Twitter to discuss testing.

Thanks for reading.