Browse Source

Fixed #11603 - Added django.test.SimpleTestCase.assertFormsetError

Thank-you Martin Green for the patch.
Tim Graham 12 years ago
parent
commit
d194714c0a

+ 1 - 0
AUTHORS

@@ -253,6 +253,7 @@ answer newbie questions, and generally made Django that much better:
     pradeep.gowda@gmail.com
     Collin Grady <collin@collingrady.com>
     Gabriel Grant <g@briel.ca>
+    Martin Green
     Daniel Greenfeld
     Simon Greenhill <dev@simon.net.nz>
     Owen Griffiths

+ 77 - 0
django/test/testcases.py

@@ -509,6 +509,83 @@ class SimpleTestCase(ut2.TestCase):
             self.fail(msg_prefix + "The form '%s' was not used to render the"
                       " response" % form)
 
+    def assertFormsetError(self, response, formset, form_index, field, errors,
+                           msg_prefix=''):
+        """
+        Asserts that a formset used to render the response has a specific error.
+
+        For field errors, specify the ``form_index`` and the ``field``.
+        For non-field errors, specify the ``form_index`` and the ``field`` as
+        None.
+        For non-form errors, specify ``form_index`` as None and the ``field``
+        as None.
+        """
+        # Add punctuation to msg_prefix
+        if msg_prefix:
+            msg_prefix += ": "
+
+        # Put context(s) into a list to simplify processing.
+        contexts = to_list(response.context)
+        if not contexts:
+            self.fail(msg_prefix + 'Response did not use any contexts to '
+                      'render the response')
+
+        # Put error(s) into a list to simplify processing.
+        errors = to_list(errors)
+
+        # Search all contexts for the error.
+        found_formset = False
+        for i, context in enumerate(contexts):
+            if formset not in context:
+                continue
+            found_formset = True
+            for err in errors:
+                if field is not None:
+                    if field in context[formset].forms[form_index].errors:
+                        field_errors = context[formset].forms[form_index].errors[field]
+                        self.assertTrue(err in field_errors,
+                                msg_prefix + "The field '%s' on formset '%s', "
+                                "form %d in context %d does not contain the "
+                                "error '%s' (actual errors: %s)" %
+                                        (field, formset, form_index, i, err,
+                                        repr(field_errors)))
+                    elif field in context[formset].forms[form_index].fields:
+                        self.fail(msg_prefix + "The field '%s' "
+                                  "on formset '%s', form %d in "
+                                  "context %d contains no errors" %
+                                        (field, formset, form_index, i))
+                    else:
+                        self.fail(msg_prefix + "The formset '%s', form %d in "
+                                 "context %d does not contain the field '%s'" %
+                                        (formset, form_index, i, field))
+                elif form_index is not None:
+                    non_field_errors = context[formset].forms[form_index].non_field_errors()
+                    self.assertFalse(len(non_field_errors) == 0,
+                                msg_prefix + "The formset '%s', form %d in "
+                                "context %d does not contain any non-field "
+                                "errors." % (formset, form_index, i))
+                    self.assertTrue(err in non_field_errors,
+                                    msg_prefix + "The formset '%s', form %d "
+                                    "in context %d does not contain the "
+                                    "non-field error '%s' "
+                                    "(actual errors: %s)" %
+                                        (formset, form_index, i, err,
+                                         repr(non_field_errors)))
+                else:
+                    non_form_errors = context[formset].non_form_errors()
+                    self.assertFalse(len(non_form_errors) == 0,
+                                     msg_prefix + "The formset '%s' in "
+                                     "context %d does not contain any "
+                                     "non-form errors." % (formset, i))
+                    self.assertTrue(err in non_form_errors,
+                                    msg_prefix + "The formset '%s' in context "
+                                    "%d does not contain the "
+                                    "non-form error '%s' (actual errors: %s)" %
+                                      (formset, i, err, repr(non_form_errors)))
+        if not found_formset:
+            self.fail(msg_prefix + "The formset '%s' was not used to render "
+                      "the response" % formset)
+
     def assertTemplateUsed(self, response=None, template_name=None, msg_prefix=''):
         """
         Asserts that the template with the provided name was used in rendering

+ 4 - 0
docs/releases/1.6.txt

@@ -283,6 +283,10 @@ Minor features
 * The :meth:`~django.db.models.query.QuerySet.get_or_create` method no longer
   requires at least one keyword argument.
 
+* The :class:`~django.test.SimpleTestCase` class includes a new assertion
+  helper for testing formset errors:
+  :meth:`~django.test.SimpleTestCase.assertFormsetError`.
+
 Backwards incompatible changes in 1.6
 =====================================
 

+ 21 - 0
docs/topics/testing/overview.txt

@@ -1532,6 +1532,27 @@ your test suite.
     ``errors`` is an error string, or a list of error strings, that are
     expected as a result of form validation.
 
+.. method:: SimpleTestCase.assertFormsetError(response, formset, form_index, field, errors, msg_prefix='')
+
+    .. versionadded:: 1.6
+
+    Asserts that the ``formset`` raises the provided list of errors when
+    rendered.
+
+    ``formset`` is the name the ``Formset`` instance was given in the template
+    context.
+
+    ``form_index`` is the number of the form within the ``Formset``.  If
+    ``form_index`` has a value of ``None``, non-form errors (errors you can
+    access via ``formset.non_form_errors()``) will be checked.
+
+    ``field`` is the name of the field on the form to check. If ``field``
+    has a value of ``None``, non-field errors (errors you can access via
+    ``form.non_field_errors()``) will be checked.
+
+    ``errors`` is an error string, or a list of error strings, that are
+    expected as a result of form validation.
+
 .. method:: SimpleTestCase.assertContains(response, text, count=None, status_code=200, msg_prefix='', html=False)
 
     Asserts that a ``Response`` instance produced the given ``status_code`` and

+ 1 - 0
tests/test_client/urls.py

@@ -21,6 +21,7 @@ urlpatterns = patterns('',
     (r'^bad_view/$', views.bad_view),
     (r'^form_view/$', views.form_view),
     (r'^form_view_with_template/$', views.form_view_with_template),
+    (r'^formset_view/$', views.formset_view),
     (r'^login_protected_view/$', views.login_protected_view),
     (r'^login_protected_method_view/$', views.login_protected_method_view),
     (r'^login_protected_view_custom_redirect/$', views.login_protected_view_changed_redirect),

+ 45 - 1
tests/test_client/views.py

@@ -7,7 +7,8 @@ from xml.dom.minidom import parseString
 from django.contrib.auth.decorators import login_required, permission_required
 from django.core import mail
 from django.forms import fields
-from django.forms.forms import Form
+from django.forms.forms import Form, ValidationError
+from django.forms.formsets import formset_factory, BaseFormSet
 from django.http import HttpResponse, HttpResponseRedirect, HttpResponseNotFound
 from django.shortcuts import render_to_response
 from django.template import Context, Template
@@ -95,6 +96,12 @@ class TestForm(Form):
     single = fields.ChoiceField(choices=TestChoices)
     multi = fields.MultipleChoiceField(choices=TestChoices)
 
+    def clean(self):
+        cleaned_data = self.cleaned_data
+        if cleaned_data.get("text") == "Raise non-field error":
+            raise ValidationError("Non-field error.")
+        return cleaned_data
+
 def form_view(request):
     "A view that tests a simple form"
     if request.method == 'POST':
@@ -130,6 +137,43 @@ def form_view_with_template(request):
         }
     )
 
+class BaseTestFormSet(BaseFormSet):
+    def clean(self):
+        """Checks that no two email addresses are the same."""
+        if any(self.errors):
+            # Don't bother validating the formset unless each form is valid
+            return
+
+        emails = []
+        for i in range(0, self.total_form_count()):
+            form = self.forms[i]
+            email = form.cleaned_data['email']
+            if email in emails:
+                raise ValidationError(
+                    "Forms in a set must have distinct email addresses."
+                )
+            emails.append(email)
+
+TestFormSet = formset_factory(TestForm, BaseTestFormSet)
+
+def formset_view(request):
+    "A view that tests a simple formset"
+    if request.method == 'POST':
+        formset = TestFormSet(request.POST)
+        if formset.is_valid():
+            t = Template('Valid POST data.', name='Valid POST Template')
+            c = Context()
+        else:
+            t = Template('Invalid POST data. {{ my_formset.errors }}',
+                         name='Invalid POST Template')
+            c = Context({'my_formset': formset})
+    else:
+        formset = TestForm(request.GET)
+        t = Template('Viewing base formset. {{ my_formset }}.',
+                     name='Formset GET Template')
+        c = Context({'my_formset': formset})
+    return HttpResponse(t.render(c))
+
 def login_protected_view(request):
     "A simple view that is login protected."
     t = Template('This is a login protected test. Username is {{ user.username }}.', name='Login Template')

+ 191 - 0
tests/test_client_regress/tests.py

@@ -543,6 +543,197 @@ class AssertFormErrorTests(TestCase):
         except AssertionError as e:
             self.assertIn("abc: The form 'form' in context 0 does not contain the non-field error 'Some error.' (actual errors: )", str(e))
 
+class AssertFormsetErrorTests(TestCase):
+    msg_prefixes = [("", {}), ("abc: ", {"msg_prefix": "abc"})]
+    def setUp(self):
+        """Makes response object for testing field and non-field errors"""
+        # For testing field and non-field errors
+        self.response_form_errors = self.getResponse({
+            'form-TOTAL_FORMS': '2',
+            'form-INITIAL_FORMS': '2',
+            'form-0-text': 'Raise non-field error',
+            'form-0-email': 'not an email address',
+            'form-0-value': 37,
+            'form-0-single': 'b',
+            'form-0-multi': ('b','c','e'),
+            'form-1-text': 'Hello World',
+            'form-1-email': 'email@domain.com',
+            'form-1-value': 37,
+            'form-1-single': 'b',
+            'form-1-multi': ('b','c','e'),
+        })
+        # For testing non-form errors
+        self.response_nonform_errors = self.getResponse({
+            'form-TOTAL_FORMS': '2',
+            'form-INITIAL_FORMS': '2',
+            'form-0-text': 'Hello World',
+            'form-0-email': 'email@domain.com',
+            'form-0-value': 37,
+            'form-0-single': 'b',
+            'form-0-multi': ('b','c','e'),
+            'form-1-text': 'Hello World',
+            'form-1-email': 'email@domain.com',
+            'form-1-value': 37,
+            'form-1-single': 'b',
+            'form-1-multi': ('b','c','e'),
+        })
+
+    def getResponse(self, post_data):
+        response = self.client.post('/test_client/formset_view/', post_data)
+        self.assertEqual(response.status_code, 200)
+        self.assertTemplateUsed(response, "Invalid POST Template")
+        return response
+
+    def test_unknown_formset(self):
+        "An assertion is raised if the formset name is unknown"
+        for prefix, kwargs in self.msg_prefixes:
+            with self.assertRaises(AssertionError) as cm:
+                self.assertFormsetError(self.response_form_errors,
+                                        'wrong_formset',
+                                        0,
+                                        'Some_field',
+                                        'Some error.',
+                                        **kwargs)
+            self.assertIn(prefix + "The formset 'wrong_formset' was not "
+                                   "used to render the response",
+                          str(cm.exception))
+
+    def test_unknown_field(self):
+        "An assertion is raised if the field name is unknown"
+        for prefix, kwargs in self.msg_prefixes:
+            with self.assertRaises(AssertionError) as cm:
+                self.assertFormsetError(self.response_form_errors,
+                                        'my_formset',
+                                        0,
+                                        'Some_field',
+                                        'Some error.',
+                                        **kwargs)
+            self.assertIn(prefix + "The formset 'my_formset', "
+                                   "form 0 in context 0 "
+                                   "does not contain the field 'Some_field'",
+                          str(cm.exception))
+
+    def test_no_error_field(self):
+        "An assertion is raised if the field doesn't have any errors"
+        for prefix, kwargs in self.msg_prefixes:
+            with self.assertRaises(AssertionError) as cm:
+                self.assertFormsetError(self.response_form_errors,
+                                        'my_formset',
+                                        1,
+                                        'value',
+                                        'Some error.',
+                                        **kwargs)
+            self.assertIn(prefix + "The field 'value' "
+                                   "on formset 'my_formset', form 1 "
+                                   "in context 0 contains no errors",
+                          str(cm.exception))
+
+    def test_unknown_error(self):
+        "An assertion is raised if the field doesn't contain the specified error"
+        for prefix, kwargs in self.msg_prefixes:
+            with self.assertRaises(AssertionError) as cm:
+                self.assertFormsetError(self.response_form_errors,
+                                        'my_formset',
+                                        0,
+                                        'email',
+                                        'Some error.',
+                                        **kwargs)
+            self.assertIn(str_prefix(prefix + "The field 'email' "
+                "on formset 'my_formset', form 0 in context 0 does not "
+                "contain the error 'Some error.' (actual errors: "
+                "[%(_)s'Enter a valid email address.'])"),
+                str(cm.exception))
+
+    def test_field_error(self):
+        "No assertion is raised if the field contains the provided error"
+        for prefix, kwargs in self.msg_prefixes:
+            self.assertFormsetError(self.response_form_errors,
+                                    'my_formset',
+                                    0,
+                                    'email',
+                                    ['Enter a valid email address.'],
+                                    **kwargs)
+
+    def test_no_nonfield_error(self):
+        "An assertion is raised if the formsets non-field errors doesn't contain any errors."
+        for prefix, kwargs in self.msg_prefixes:
+            with self.assertRaises(AssertionError) as cm:
+                self.assertFormsetError(self.response_form_errors,
+                                        'my_formset',
+                                        1,
+                                        None,
+                                        'Some error.',
+                                        **kwargs)
+            self.assertIn(prefix + "The formset 'my_formset', form 1 in "
+                                   "context 0 does not contain any "
+                                   "non-field errors.",
+                          str(cm.exception))
+
+    def test_unknown_nonfield_error(self):
+        "An assertion is raised if the formsets non-field errors doesn't contain the provided error."
+        for prefix, kwargs in self.msg_prefixes:
+            with self.assertRaises(AssertionError) as cm:
+                self.assertFormsetError(self.response_form_errors,
+                                        'my_formset',
+                                        0,
+                                        None,
+                                        'Some error.',
+                                        **kwargs)
+            self.assertIn(str_prefix(prefix +
+                "The formset 'my_formset', form 0 in context 0 does not "
+                "contain the non-field error 'Some error.' (actual errors: "
+                "[%(_)s'Non-field error.'])"), str(cm.exception))
+
+    def test_nonfield_error(self):
+        "No assertion is raised if the formsets non-field errors contains the provided error."
+        for prefix, kwargs in self.msg_prefixes:
+            self.assertFormsetError(self.response_form_errors,
+                                    'my_formset',
+                                    0,
+                                    None,
+                                    'Non-field error.',
+                                    **kwargs)
+
+    def test_no_nonform_error(self):
+        "An assertion is raised if the formsets non-form errors doesn't contain any errors."
+        for prefix, kwargs in self.msg_prefixes:
+            with self.assertRaises(AssertionError) as cm:
+                self.assertFormsetError(self.response_form_errors,
+                                        'my_formset',
+                                        None,
+                                        None,
+                                        'Some error.',
+                                        **kwargs)
+            self.assertIn(prefix + "The formset 'my_formset' in context 0 "
+                                   "does not contain any non-form errors.",
+                          str(cm.exception))
+
+    def test_unknown_nonform_error(self):
+        "An assertion is raised if the formsets non-form errors doesn't contain the provided error."
+        for prefix, kwargs in self.msg_prefixes:
+            with self.assertRaises(AssertionError) as cm:
+                self.assertFormsetError(self.response_nonform_errors,
+                                        'my_formset',
+                                        None,
+                                        None,
+                                        'Some error.',
+                                        **kwargs)
+            self.assertIn(str_prefix(prefix +
+                "The formset 'my_formset' in context 0 does not contain the "
+                "non-form error 'Some error.' (actual errors: [%(_)s'Forms "
+                "in a set must have distinct email addresses.'])"), str(cm.exception))
+
+    def test_nonform_error(self):
+        "No assertion is raised if the formsets non-form errors contains the provided error."
+        for prefix, kwargs in self.msg_prefixes:
+            self.assertFormsetError(self.response_nonform_errors,
+                                    'my_formset',
+                                    None,
+                                    None,
+                                    'Forms in a set must have distinct email '
+                                    'addresses.',
+                                    **kwargs)
+
 @override_settings(PASSWORD_HASHERS=('django.contrib.auth.hashers.SHA1PasswordHasher',))
 class LoginTests(TestCase):
     fixtures = ['testdata']