Testing Django apps

Jul 15, 2008 Django

Running tests

To launch testing, run manage.py from the project folder, passing in 'test' as an argument:
./manage.py test

Writing tests

You can embed doctests in your models.py, or create a separate tests.py (it should live at the app level, in the same place as models.py and views.py) for unit testing. (I'm not doing doctests yet, btw - this post focuses only on unit tests.)
/myproject/myapp/tests.py

Writing unit tests for models

Writing unit tests for models in your Django application is extremely straightforward. Start by importing the Python unittest module:
import unittest
And the models and model classes you're testing:
from myapp.models import RegistrationProfile, UserProfile
Create a class for the model to test:
class UserProfileTestCase(unittest.TestCase):
In that class, write a setUp method where you create a few objects with dummy data. Maybe it's obvious, but the k/v pairs you're passing in to create() should map to your model:
    def setUp(self):
        self.profile1 = UserProfile.objects.create(id=1, user_id=1, first_name='John', 
                        last_name='Smith', address='123 Main Street', favorite_cheese='cheddar')
        self.profile2 = UserProfile.objects.create(id=2, user_id=2, first_name='Jane', 
                        last_name='Doe', address='23 Park Avenue', favorite_cheese='swiss')
Then write a second method to test assertions against these objects:
    def test_something(self):
        self.assertEquals(self.profile1.user_id, 1)
        self.assertEquals(self.profile2.user_id, 2)
        profile = UserProfile.objects.get(pk=1)

        self.assertEquals(profile.first_name, 'John')
        self.profile1.first_name = 'Mark'

        self.assertNotEquals(self.profile1.first_name, 'John')
        self.assertEquals(self.profile1.first_name, 'Mark')
Okay, so that wasn't such an exciting example, but it works.

Writing unit tests for views

Writing tests for views is a little different. There's not much in the way of documentation or examples out there, so hopefully this will help add to the pool. When manage.py runs, it only looks for tests in models.py and tests.py. I'm not sure why that is, but it's in black and white (or rather black and green and white) here. So writing doctests in my views.py is out. I wouldn't want to use them in my views in any case - every one of my view methods takes 'request' as an argument, and I wouldn't even know where to begin setting up a compllete set of dummy request data to test inline. To interact with the views, you need an instance of the test client Basically, the test client allows you to simulate GET and POST requests, then test assertions against the responses. In other words, suppose you have this pattern in your urls.py:
    url(r'^/accounts/register/$', views.register, name='myapp_register'),
Using the test client to GET '/accounts/register/' would in turn test the associated view method 'register', and any other methods that views.register has dependencies on. ..... Start by importing the Client and TestCase modules:
from django.test import Client, TestCase
Create a class that extends TestCase (it doesn't matter what it's named):
class ViewTests(TestCase):
The first method just needs to instantiate the test client:
    def setUp(self):
        self.client = Client()
The second method is the test method - I named it for the corresponding view method it's testing, just for convenience:
    def test_register(self):
        response = self.client.get('/accounts/register/')
        self.assertEqual(response.status_code, 200)
        self.assertContains(response, 'Register Your Account')

        response = self.client.post('/accounts/register/', {'username': 'myname', 'password': 'mypassword'})
        self.assertEqual(response.status_code, 200)
For a much more detailed example, take a look at this piece of Django documentation: Testing using the Test Client

Some view testing gotchas

When I ran my model tests, they passed immediately, but they were all simple so that's not surprising. But when I started adding tests on the views, I ran into a few problems, all related to project settings. The first one that came up was:
    File "/Users/bshaurette/Code/django_trunk/django/core/handlers/base.py",
        line 126, in get_response
    subject = 'Error (%s IP): %s' % ((request.META.get('REMOTE_ADDR') in settings.INTERNAL_IPS and 'internal' or 'EXTERNAL'), request.path)
    TypeError: 'in ' requires string as left operand
I'm not sure why I needed to have an INTERNAL_IPS value set, but I didn't have it in my project's settings.py, so I added it to the top of the tests.py instead:
INTERNAL_IPS = ('127.0.0.1',)
After that, I hit an error related to a variable that couldn't be found in one of the project urls:
ImproperlyConfigured: Error while importing URLconf 'myproject.urls': 'MEDIA_LOCATION'
In the project's urls.py, one of the patterns included an environment variable - the location for media (style sheets):
    (r'^site_media/(?P .*)$', 'django.views.static.serve', {'document_root': os.environ['MEDIA_LOCATION']})
So I also gave that variable a value in my test file:
os.environ['MEDIA_LOCATION'] = '/Users/myuser/myproject/media/'
Finally, I got this error:
DoesNotExist: Site matching query does not exist.
In my project's settings.py, I had the SITE_ID value set to '2' (I had been playing with different site values for my registration and confirmation emails earlier). Setting it back to '1' seemed to fix the problem:
SITE_ID = 1
Apparently, if that value is anything other than '1', your tests with the client won't run: Ticket #5979: Django tests fail if SITE_ID is not 1 My final tests.py for views looks something like this:
import os
from django.test import Client, TestCase

os.environ['MEDIA_LOCATION'] = '/Users/bshaurette/tippit/openmedia_project/public'
INTERNAL_IPS = ('127.0.0.1',)

class ViewTests(TestCase):
    def setUp(self):
        self.client = Client()

    def test_register(self):
        response = self.client.get('/accounts/register/')
        self.assertEqual(response.status_code, 200)
        self.assertContains(response, 'Register Your Account')

        response = self.client.post('/accounts/register/', {'username': 'myname', 'password': 'mypassword'})
        self.assertEqual(response.status_code, 200)

Additional links

Testing Django applications For some additional assertions you can use in your tests, go here: 23.3.5 TestCase Objects (Python Library Reference) A good step-by-step example of model testing: Two-Faced Django Part 2: Models and Django Testing (Will Larson) Oh, here's one other thing to watch out for - if you have a tests.py file in an app folder, you must also have a models.py in that app, even if it's not being used. For some reason, unit testing won't run on an app folder that doesn't have a models.py (it can be blank, but it must be present): Why doesn't Django find my unit tests? The discussion I opened on the Django Users group on Google: writing unit tests for views?