Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to write a unit test for a django view?

I have problems understanding how the unit tests should be designed for django.

From my understanding testing the whole view in one go seems impossible. We need to distinguish between pre-post and post states of request. But I have no idea how to design this. Is there any real life example?

Looking at the documentation the examples are too simplified and only focused on the model.

@login_required def call_view(request, contact_id):     profile = request.user.get_profile()     if request.POST:                 form = CallsForm(profile.company, request.POST)                    if form.is_valid()         return HttpResponseRedirect('/contact/' + contact_id + '/calls/')     else:                 form = CallsForm(profile.company, instance=call)                   variables = RequestContext(request, {'form':form}     return render_to_response('conversation.html', variables) 

update:

trying to make a success test work, but it still fails:

def test_contact_view_success(self):     # same again, but with valid data, then     self.client.login(username='username1', password='password1')     response = self.client.post('/contact/add/', {u'last_name': [u'Johnson'], })      self.assertRedirects(response, '/') 

error message:

AssertionError: Response didn't redirect as expected: Response code was 200 (expected 302) 

I think this is because the form.is_valid() fails and it doesn't redirect, correct?

like image 705
Houman Avatar asked Aug 09 '12 14:08

Houman


People also ask

How do I write unit tests in Django?

Writing tests Django's unit tests use a Python standard library module: unittest . This module defines tests using a class-based approach. When you run your tests, the default behavior of the test utility is to find all the test cases (that is, subclasses of unittest.

How do I test my Django site?

Django comes with a small set of its own tools for writing tests, notably a test client and four provided test case classes. These classes rely on Python's unittest module and TestCase base class. The Django test client can be used to act like a dummy web browser and check views.

How do I test a Django project?

The preferred way to write tests in Django is using the unittest module built-in to the Python standard library. This is covered in detail in the Writing and running tests document. You can also use any other Python test framework; Django provides an API and tools for that kind of integration.


1 Answers

NB NB! This isn't strictly a "unit test"; it's difficult and uncommon to write an independent unit test for Django view code. This is more of an integration test...

You're right that there are several pathways through your view:

  1. GET or POST by anonymous user (should redirect to login page)
  2. GET or POST by logged-in user with no profile (should raise a UserProfile.DoesNotExist exception)
  3. GET by logged-in user (should show the form)
  4. POST by logged-in user with blank data (should show form errors)
  5. POST by logged-in user with invalid data (should show form errors)
  6. POST by logged-in user with valid data (should redirect)

Testing 1 is really just testing @login_required, so you could skip it. I tend to test it anyway (just in case we've forgotten to use that decorator).

I'm not sure the failure case (a 500 error page) in 2 is what you really want. I would work out what you want to happen instead (perhaps use get_or_create(), or catch the DoesNotExist exception and create a new profile that way), or redirect to a page for the user to create a profile.

Depending on how much custom validation you have, 4 may not really need to be tested.

In any case, given all of the above, I would do something like:

from django.test import TestCase  class TestCalls(TestCase):     def test_call_view_deny_anonymous(self):         response = self.client.get('/url/to/view', follow=True)         self.assertRedirects(response, '/login/')         response = self.client.post('/url/to/view', follow=True)         self.assertRedirects(response, '/login/')      def test_call_view_load(self):         self.client.login(username='user', password='test')  # defined in fixture or with factory in setUp()         response = self.client.get('/url/to/view')         self.assertEqual(response.status_code, 200)         self.assertTemplateUsed(response, 'conversation.html')      def test_call_view_fail_blank(self):         self.client.login(username='user', password='test')         response = self.client.post('/url/to/view', {}) # blank data dictionary         self.assertFormError(response, 'form', 'some_field', 'This field is required.')         # etc. ...      def test_call_view_fail_invalid(self):         # as above, but with invalid rather than blank data in dictionary      def test_call_view_success_invalid(self):         # same again, but with valid data, then         self.assertRedirects(response, '/contact/1/calls/') 

Obviously, a drawback here is hard-coded URLs. You could either use reverse() in your tests or build requests using RequestFactory and call your views as methods (rather than by URL). With the latter method, though, you still need to use hard-coded values or reverse() to test redirect targets.

Hope this helps.

like image 58
supervacuo Avatar answered Oct 01 '22 04:10

supervacuo