113

I'd like to simulate requests to my views in Django when I'm writing tests. This is mainly to test the forms. Here's a snippet of a simple test request:

from django.tests import TestCase

class MyTests(TestCase):
    def test_forms(self):
        response = self.client.post("/my/form/", {'something':'something'})
        self.assertEqual(response.status_code, 200) # we get our page back with an error

The page always returns a response of 200 whether there's an form error or not. How can I check that my Form failed and that the particular field (soemthing) had an error?

Mridang Agarwalla
  • 38,521
  • 65
  • 199
  • 353

3 Answers3

256

I think if you just want to test the form, then you should just test the form and not the view where the form is rendered. Example to get an idea:

from django.test import TestCase
from myapp.forms import MyForm

class MyTests(TestCase):
    def test_forms(self):
        form_data = {'something': 'something'}
        form = MyForm(data=form_data)
        self.assertTrue(form.is_valid())
        ... # other tests relating forms, for example checking the form data
rnevius
  • 24,711
  • 10
  • 51
  • 76
Torsten Engelbrecht
  • 12,227
  • 4
  • 39
  • 47
  • 63
    +1. The idea of **unit** tests is to test each unit separately. – Daniel Roseman Sep 05 '11 at 08:11
  • 13
    @Daniel But integration tests are way more useful and more likely to catch bugs. – wobbily_col Feb 27 '14 at 11:57
  • 19
    @wobbily_col It also takes more time to spot what is the actual bug in an integration test. In a unit test its more obvious. I think for a good test coverage you need anyway both of them though. – Torsten Engelbrecht Feb 28 '14 at 09:36
  • 2
    @Danbiel Sure. I think they are a good fit for certain units of functionality, but a lot of the time people just seem to test the framework / library they are using. In general I would say integration tests are more valuable. – wobbily_col Feb 28 '14 at 10:41
  • 11
    This is how you check for a specific form error: ```self.assertEquals(form.errors['recipient'], [u"That recipient isn't valid"])``` – Emil Stenström Jun 30 '14 at 14:54
  • 18
    `self.assertEqual(form.is_valid(), True)` could be simplified: `self.assertTrue(form.is_valid())` – Adam Taylor Aug 22 '14 at 21:10
  • 3
    I like doing `self.assertTrue(form.isVald(), form.errors)` - that ensures that the test failure message includes the reasons why the form failed to validate. – M. Scott Ford Oct 10 '17 at 15:51
79

https://docs.djangoproject.com/en/stable/topics/testing/tools/#django.test.SimpleTestCase.assertFormError

from django.tests import TestCase

class MyTests(TestCase):
    def test_forms(self):
        response = self.client.post("/my/form/", {'something':'something'})
        self.assertFormError(response, 'form', 'something', 'This field is required.')

Where "form" is the context variable name for your form, "something" is the field name, and "This field is required." is the exact text of the expected validation error.

Flimm
  • 97,949
  • 30
  • 201
  • 217
Shane
  • 1,001
  • 6
  • 7
  • This raises an AttibuteError for me: AttributeError: 'SafeUnicode' object has no attribute 'errors' – sbaechler May 15 '12 at 09:45
  • for newbie users: create a user beforehand and use `self.client.force_login(self.user)` as first line in the test method. – sgauri Mar 15 '18 at 09:30
  • I had an issue with this post(), then I figured out that I had to send it as multipart as following response = self.client.post("/form-url/", data={ 'name': 'test123', 'category': 1, 'note': 'note123' }, content_type=django.test.client.MULTIPART_CONTENT) If any stuck with getting empty instance when saving the form, then check the requests sent from browser – Ghaleb Khaled Dec 26 '18 at 15:46
19

The original 2011 answer was

self.assertContains(response, "Invalid message here", 1, 200)

But I see now (2018) there is a whole crowd of applicable asserts available:

  • assertRaisesMessage
  • assertFieldOutput
  • assertFormError
  • assertFormsetError

Take your pick.

Braiam
  • 4,345
  • 11
  • 47
  • 69
John Mee
  • 44,003
  • 31
  • 133
  • 171