From 7ca9d9306c5e5e175668af513725a16ba3b26d9d Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Fri, 22 Jan 2010 15:02:02 +0000 Subject: [PATCH] Fixed #10314 -- Added a message prefix argument to Django's test assertions. Thanks to Wes Winham for the original suggestion, and Chistian Oudard for the patch. git-svn-id: http://code.djangoproject.com/svn/django/trunk@12273 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- AUTHORS | 1 + django/test/testcases.py | 127 +++++++++++------- docs/topics/testing.txt | 21 ++- .../test_client_regress/models.py | 109 ++++++++++++++- 4 files changed, 200 insertions(+), 58 deletions(-) diff --git a/AUTHORS b/AUTHORS index efc43fa3e83..518b4f14ae3 100644 --- a/AUTHORS +++ b/AUTHORS @@ -339,6 +339,7 @@ answer newbie questions, and generally made Django that much better: Afonso Fernández Nogueira Neal Norwitz Todd O'Bryan + Christian Oudard oggie rob oggy Jay Parlar diff --git a/django/test/testcases.py b/django/test/testcases.py index 2f33acec0b0..7ae1cbb4ecd 100644 --- a/django/test/testcases.py +++ b/django/test/testcases.py @@ -287,34 +287,41 @@ class TransactionTestCase(unittest.TestCase): clear_url_caches() def assertRedirects(self, response, expected_url, status_code=302, - target_status_code=200, host=None): + target_status_code=200, host=None, msg_prefix=''): """Asserts that a response redirected to a specific URL, and that the redirect URL can be loaded. Note that assertRedirects won't work for external links since it uses TestClient to do a request. """ + if msg_prefix: + msg_prefix += ": " + if hasattr(response, 'redirect_chain'): # The request was a followed redirect self.failUnless(len(response.redirect_chain) > 0, - ("Response didn't redirect as expected: Response code was %d" - " (expected %d)" % (response.status_code, status_code))) + msg_prefix + "Response didn't redirect as expected: Response" + " code was %d (expected %d)" % + (response.status_code, status_code)) self.assertEqual(response.redirect_chain[0][1], status_code, - ("Initial response didn't redirect as expected: Response code was %d" - " (expected %d)" % (response.redirect_chain[0][1], status_code))) + msg_prefix + "Initial response didn't redirect as expected:" + " Response code was %d (expected %d)" % + (response.redirect_chain[0][1], status_code)) url, status_code = response.redirect_chain[-1] self.assertEqual(response.status_code, target_status_code, - ("Response didn't redirect as expected: Final Response code was %d" - " (expected %d)" % (response.status_code, target_status_code))) + msg_prefix + "Response didn't redirect as expected: Final" + " Response code was %d (expected %d)" % + (response.status_code, target_status_code)) else: # Not a followed redirect self.assertEqual(response.status_code, status_code, - ("Response didn't redirect as expected: Response code was %d" - " (expected %d)" % (response.status_code, status_code))) + msg_prefix + "Response didn't redirect as expected: Response" + " code was %d (expected %d)" % + (response.status_code, status_code)) url = response['Location'] scheme, netloc, path, query, fragment = urlsplit(url) @@ -324,9 +331,9 @@ class TransactionTestCase(unittest.TestCase): # Get the redirection page, using the same client that was used # to obtain the original response. self.assertEqual(redirect_response.status_code, target_status_code, - ("Couldn't retrieve redirection page '%s': response code was %d" - " (expected %d)") % - (path, redirect_response.status_code, target_status_code)) + msg_prefix + "Couldn't retrieve redirection page '%s':" + " response code was %d (expected %d)" % + (path, redirect_response.status_code, target_status_code)) e_scheme, e_netloc, e_path, e_query, e_fragment = urlsplit(expected_url) if not (e_scheme or e_netloc): @@ -334,10 +341,11 @@ class TransactionTestCase(unittest.TestCase): e_query, e_fragment)) self.assertEqual(url, expected_url, - "Response redirected to '%s', expected '%s'" % (url, expected_url)) + msg_prefix + "Response redirected to '%s', expected '%s'" % + (url, expected_url)) - - def assertContains(self, response, text, count=None, status_code=200): + def assertContains(self, response, text, count=None, status_code=200, + msg_prefix=''): """ Asserts that a response indicates that a page was retrieved successfully, (i.e., the HTTP status code was as expected), and that @@ -345,42 +353,52 @@ class TransactionTestCase(unittest.TestCase): If ``count`` is None, the count doesn't matter - the assertion is true if the text occurs at least once in the response. """ + if msg_prefix: + msg_prefix += ": " + self.assertEqual(response.status_code, status_code, - "Couldn't retrieve page: Response code was %d (expected %d)'" % - (response.status_code, status_code)) + msg_prefix + "Couldn't retrieve page: Response code was %d" + " (expected %d)" % (response.status_code, status_code)) text = smart_str(text, response._charset) real_count = response.content.count(text) if count is not None: self.assertEqual(real_count, count, - "Found %d instances of '%s' in response (expected %d)" % - (real_count, text, count)) + msg_prefix + "Found %d instances of '%s' in response" + " (expected %d)" % (real_count, text, count)) else: self.failUnless(real_count != 0, - "Couldn't find '%s' in response" % text) + msg_prefix + "Couldn't find '%s' in response" % text) - def assertNotContains(self, response, text, status_code=200): + def assertNotContains(self, response, text, status_code=200, + msg_prefix=''): """ Asserts that a response indicates that a page was retrieved successfully, (i.e., the HTTP status code was as expected), and that ``text`` doesn't occurs in the content of the response. """ - self.assertEqual(response.status_code, status_code, - "Couldn't retrieve page: Response code was %d (expected %d)'" % - (response.status_code, status_code)) - text = smart_str(text, response._charset) - self.assertEqual(response.content.count(text), - 0, "Response should not contain '%s'" % text) + if msg_prefix: + msg_prefix += ": " - def assertFormError(self, response, form, field, errors): + self.assertEqual(response.status_code, status_code, + msg_prefix + "Couldn't retrieve page: Response code was %d" + " (expected %d)" % (response.status_code, status_code)) + text = smart_str(text, response._charset) + self.assertEqual(response.content.count(text), 0, + msg_prefix + "Response should not contain '%s'" % text) + + def assertFormError(self, response, form, field, errors, msg_prefix=''): """ Asserts that a form used to render the response has a specific field error. """ + if msg_prefix: + msg_prefix += ": " + # Put context(s) into a list to simplify processing. contexts = to_list(response.context) if not contexts: - self.fail('Response did not use any contexts to render the' - ' response') + 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) @@ -396,50 +414,57 @@ class TransactionTestCase(unittest.TestCase): if field in context[form].errors: field_errors = context[form].errors[field] self.failUnless(err in field_errors, - "The field '%s' on form '%s' in" - " context %d does not contain the" - " error '%s' (actual errors: %s)" % - (field, form, i, err, - repr(field_errors))) + msg_prefix + "The field '%s' on form '%s' in" + " context %d does not contain the error '%s'" + " (actual errors: %s)" % + (field, form, i, err, repr(field_errors))) elif field in context[form].fields: - self.fail("The field '%s' on form '%s' in context %d" - " contains no errors" % (field, form, i)) + self.fail(msg_prefix + "The field '%s' on form '%s'" + " in context %d contains no errors" % + (field, form, i)) else: - self.fail("The form '%s' in context %d does not" - " contain the field '%s'" % + self.fail(msg_prefix + "The form '%s' in context %d" + " does not contain the field '%s'" % (form, i, field)) else: non_field_errors = context[form].non_field_errors() self.failUnless(err in non_field_errors, - "The form '%s' in context %d does not contain the" - " non-field error '%s' (actual errors: %s)" % + msg_prefix + "The form '%s' in context %d does not" + " contain the non-field error '%s'" + " (actual errors: %s)" % (form, i, err, non_field_errors)) if not found_form: - self.fail("The form '%s' was not used to render the response" % - form) + self.fail(msg_prefix + "The form '%s' was not used to render the" + " response" % form) - def assertTemplateUsed(self, response, template_name): + def assertTemplateUsed(self, response, template_name, msg_prefix=''): """ Asserts that the template with the provided name was used in rendering the response. """ + if msg_prefix: + msg_prefix += ": " + template_names = [t.name for t in to_list(response.template)] if not template_names: - self.fail('No templates used to render the response') + self.fail(msg_prefix + "No templates used to render the response") self.failUnless(template_name in template_names, - (u"Template '%s' was not a template used to render the response." - u" Actual template(s) used: %s") % (template_name, - u', '.join(template_names))) + msg_prefix + "Template '%s' was not a template used to render" + " the response. Actual template(s) used: %s" % + (template_name, u', '.join(template_names))) - def assertTemplateNotUsed(self, response, template_name): + def assertTemplateNotUsed(self, response, template_name, msg_prefix=''): """ Asserts that the template with the provided name was NOT used in rendering the response. """ + if msg_prefix: + msg_prefix += ": " + template_names = [t.name for t in to_list(response.template)] self.failIf(template_name in template_names, - (u"Template '%s' was used unexpectedly in rendering the" - u" response") % template_name) + msg_prefix + "Template '%s' was used unexpectedly in rendering" + " the response" % template_name) def connections_support_transactions(): """ diff --git a/docs/topics/testing.txt b/docs/topics/testing.txt index 0b205cee356..b515d2fac96 100644 --- a/docs/topics/testing.txt +++ b/docs/topics/testing.txt @@ -1104,23 +1104,32 @@ Assertions .. versionadded:: 1.0 +.. versionchanged:: 1.2 + Addded ``msg_prefix`` argument. + As Python's normal ``unittest.TestCase`` class implements assertion methods such as ``assertTrue`` and ``assertEquals``, Django's custom ``TestCase`` class provides a number of custom assertion methods that are useful for testing Web applications: -.. method:: TestCase.assertContains(response, text, count=None, status_code=200) +The failure messages given by the assertion methods can be customized +with the ``msg_prefix`` argument. This string will be prefixed to any +failure message generated by the assertion. This allows you to provide +additional details that may help you to identify the location and +cause of an failure in your test suite. + +.. method:: TestCase.assertContains(response, text, count=None, status_code=200, msg_prefix='') Asserts that a ``Response`` instance produced the given ``status_code`` and that ``text`` appears in the content of the response. If ``count`` is provided, ``text`` must occur exactly ``count`` times in the response. -.. method:: TestCase.assertNotContains(response, text, status_code=200) +.. method:: TestCase.assertNotContains(response, text, status_code=200, msg_prefix='') Asserts that a ``Response`` instance produced the given ``status_code`` and that ``text`` does not appears in the content of the response. -.. method:: TestCase.assertFormError(response, form, field, errors) +.. method:: TestCase.assertFormError(response, form, field, errors, msg_prefix='') Asserts that a field on a form raises the provided list of errors when rendered on the form. @@ -1135,19 +1144,19 @@ applications: ``errors`` is an error string, or a list of error strings, that are expected as a result of form validation. -.. method:: TestCase.assertTemplateUsed(response, template_name) +.. method:: TestCase.assertTemplateUsed(response, template_name, msg_prefix='') Asserts that the template with the given name was used in rendering the response. The name is a string such as ``'admin/index.html'``. -.. method:: TestCase.assertTemplateNotUsed(response, template_name) +.. method:: TestCase.assertTemplateNotUsed(response, template_name, msg_prefix='') Asserts that the template with the given name was *not* used in rendering the response. -.. method:: TestCase.assertRedirects(response, expected_url, status_code=302, target_status_code=200) +.. method:: TestCase.assertRedirects(response, expected_url, status_code=302, target_status_code=200, msg_prefix='') Asserts that the response return a ``status_code`` redirect status, it redirected to ``expected_url`` (including any GET data), and the final diff --git a/tests/regressiontests/test_client_regress/models.py b/tests/regressiontests/test_client_regress/models.py index e532c90afc6..42b763ca41f 100644 --- a/tests/regressiontests/test_client_regress/models.py +++ b/tests/regressiontests/test_client_regress/models.py @@ -31,40 +31,86 @@ class AssertContainsTests(TestCase): self.assertContains(response, 'twice') self.assertContains(response, 'twice', 2) + try: + self.assertContains(response, 'text', status_code=999) + except AssertionError, e: + self.assertEquals(str(e), "Couldn't retrieve page: Response code was 200 (expected 999)") + try: + self.assertContains(response, 'text', status_code=999, msg_prefix='abc') + except AssertionError, e: + self.assertEquals(str(e), "abc: Couldn't retrieve page: Response code was 200 (expected 999)") + + try: + self.assertNotContains(response, 'text', status_code=999) + except AssertionError, e: + self.assertEquals(str(e), "Couldn't retrieve page: Response code was 200 (expected 999)") + try: + self.assertNotContains(response, 'text', status_code=999, msg_prefix='abc') + except AssertionError, e: + self.assertEquals(str(e), "abc: Couldn't retrieve page: Response code was 200 (expected 999)") + try: self.assertNotContains(response, 'once') except AssertionError, e: self.assertEquals(str(e), "Response should not contain 'once'") + try: + self.assertNotContains(response, 'once', msg_prefix='abc') + except AssertionError, e: + self.assertEquals(str(e), "abc: Response should not contain 'once'") try: self.assertContains(response, 'never', 1) except AssertionError, e: self.assertEquals(str(e), "Found 0 instances of 'never' in response (expected 1)") + try: + self.assertContains(response, 'never', 1, msg_prefix='abc') + except AssertionError, e: + self.assertEquals(str(e), "abc: Found 0 instances of 'never' in response (expected 1)") try: self.assertContains(response, 'once', 0) except AssertionError, e: self.assertEquals(str(e), "Found 1 instances of 'once' in response (expected 0)") + try: + self.assertContains(response, 'once', 0, msg_prefix='abc') + except AssertionError, e: + self.assertEquals(str(e), "abc: Found 1 instances of 'once' in response (expected 0)") try: self.assertContains(response, 'once', 2) except AssertionError, e: self.assertEquals(str(e), "Found 1 instances of 'once' in response (expected 2)") + try: + self.assertContains(response, 'once', 2, msg_prefix='abc') + except AssertionError, e: + self.assertEquals(str(e), "abc: Found 1 instances of 'once' in response (expected 2)") try: self.assertContains(response, 'twice', 1) except AssertionError, e: self.assertEquals(str(e), "Found 2 instances of 'twice' in response (expected 1)") + try: + self.assertContains(response, 'twice', 1, msg_prefix='abc') + except AssertionError, e: + self.assertEquals(str(e), "abc: Found 2 instances of 'twice' in response (expected 1)") try: self.assertContains(response, 'thrice') except AssertionError, e: self.assertEquals(str(e), "Couldn't find 'thrice' in response") + try: + self.assertContains(response, 'thrice', msg_prefix='abc') + except AssertionError, e: + self.assertEquals(str(e), "abc: Couldn't find 'thrice' in response") try: self.assertContains(response, 'thrice', 3) except AssertionError, e: self.assertEquals(str(e), "Found 0 instances of 'thrice' in response (expected 3)") + try: + self.assertContains(response, 'thrice', 3, msg_prefix='abc') + except AssertionError, e: + self.assertEquals(str(e), "abc: Found 0 instances of 'thrice' in response (expected 3)") def test_unicode_contains(self): "Unicode characters can be found in template context" @@ -80,6 +126,7 @@ class AssertContainsTests(TestCase): self.assertNotContains(r, u'はたけ') self.assertNotContains(r, '\xe3\x81\xaf\xe3\x81\x9f\xe3\x81\x91'.decode('utf-8')) + class AssertTemplateUsedTests(TestCase): fixtures = ['testdata.json'] @@ -95,21 +142,35 @@ class AssertTemplateUsedTests(TestCase): except AssertionError, e: self.assertEquals(str(e), "No templates used to render the response") + try: + self.assertTemplateUsed(response, 'GET Template', msg_prefix='abc') + except AssertionError, e: + self.assertEquals(str(e), "abc: No templates used to render the response") + def test_single_context(self): "Template assertions work when there is a single context" response = self.client.get('/test_client/post_view/', {}) - # try: self.assertTemplateNotUsed(response, 'Empty GET Template') except AssertionError, e: self.assertEquals(str(e), "Template 'Empty GET Template' was used unexpectedly in rendering the response") + try: + self.assertTemplateNotUsed(response, 'Empty GET Template', msg_prefix='abc') + except AssertionError, e: + self.assertEquals(str(e), "abc: Template 'Empty GET Template' was used unexpectedly in rendering the response") + try: self.assertTemplateUsed(response, 'Empty POST Template') except AssertionError, e: self.assertEquals(str(e), "Template 'Empty POST Template' was not a template used to render the response. Actual template(s) used: Empty GET Template") + try: + self.assertTemplateUsed(response, 'Empty POST Template', msg_prefix='abc') + except AssertionError, e: + self.assertEquals(str(e), "abc: Template 'Empty POST Template' was not a template used to render the response. Actual template(s) used: Empty GET Template") + def test_multiple_context(self): "Template assertions work when there are multiple contexts" post_data = { @@ -146,6 +207,11 @@ class AssertRedirectsTests(TestCase): except AssertionError, e: self.assertEquals(str(e), "Response didn't redirect as expected: Response code was 301 (expected 302)") + try: + self.assertRedirects(response, '/test_client/get_view/', msg_prefix='abc') + except AssertionError, e: + self.assertEquals(str(e), "abc: Response didn't redirect as expected: Response code was 301 (expected 302)") + def test_lost_query(self): "An assertion is raised if the redirect location doesn't preserve GET parameters" response = self.client.get('/test_client/redirect_view/', {'var': 'value'}) @@ -154,6 +220,11 @@ class AssertRedirectsTests(TestCase): except AssertionError, e: self.assertEquals(str(e), "Response redirected to 'http://testserver/test_client/get_view/?var=value', expected 'http://testserver/test_client/get_view/'") + try: + self.assertRedirects(response, '/test_client/get_view/', msg_prefix='abc') + except AssertionError, e: + self.assertEquals(str(e), "abc: Response redirected to 'http://testserver/test_client/get_view/?var=value', expected 'http://testserver/test_client/get_view/'") + def test_incorrect_target(self): "An assertion is raised if the response redirects to another target" response = self.client.get('/test_client/permanent_redirect_view/') @@ -172,6 +243,12 @@ class AssertRedirectsTests(TestCase): except AssertionError, e: self.assertEquals(str(e), "Couldn't retrieve redirection page '/test_client/permanent_redirect_view/': response code was 301 (expected 200)") + try: + # The redirect target responds with a 301 code, not 200 + self.assertRedirects(response, 'http://testserver/test_client/permanent_redirect_view/', msg_prefix='abc') + except AssertionError, e: + self.assertEquals(str(e), "abc: Couldn't retrieve redirection page '/test_client/permanent_redirect_view/': response code was 301 (expected 200)") + def test_redirect_chain(self): "You can follow a redirect chain of multiple redirects" response = self.client.get('/test_client_regress/redirects/further/more/', {}, follow=True) @@ -263,6 +340,11 @@ class AssertRedirectsTests(TestCase): except AssertionError, e: self.assertEquals(str(e), "Response didn't redirect as expected: Response code was 200 (expected 302)") + try: + self.assertRedirects(response, '/test_client/get_view/', msg_prefix='abc') + except AssertionError, e: + self.assertEquals(str(e), "abc: Response didn't redirect as expected: Response code was 200 (expected 302)") + def test_redirect_on_non_redirect_page(self): "An assertion is raised if the original page couldn't be retrieved as expected" # This page will redirect with code 301, not 302 @@ -272,6 +354,11 @@ class AssertRedirectsTests(TestCase): except AssertionError, e: self.assertEquals(str(e), "Response didn't redirect as expected: Response code was 200 (expected 302)") + try: + self.assertRedirects(response, '/test_client/get_view/', msg_prefix='abc') + except AssertionError, e: + self.assertEquals(str(e), "abc: Response didn't redirect as expected: Response code was 200 (expected 302)") + class AssertFormErrorTests(TestCase): def test_unknown_form(self): @@ -291,6 +378,10 @@ class AssertFormErrorTests(TestCase): self.assertFormError(response, 'wrong_form', 'some_field', 'Some error.') except AssertionError, e: self.assertEqual(str(e), "The form 'wrong_form' was not used to render the response") + try: + self.assertFormError(response, 'wrong_form', 'some_field', 'Some error.', msg_prefix='abc') + except AssertionError, e: + self.assertEqual(str(e), "abc: The form 'wrong_form' was not used to render the response") def test_unknown_field(self): "An assertion is raised if the field name is unknown" @@ -309,6 +400,10 @@ class AssertFormErrorTests(TestCase): self.assertFormError(response, 'form', 'some_field', 'Some error.') except AssertionError, e: self.assertEqual(str(e), "The form 'form' in context 0 does not contain the field 'some_field'") + try: + self.assertFormError(response, 'form', 'some_field', 'Some error.', msg_prefix='abc') + except AssertionError, e: + self.assertEqual(str(e), "abc: The form 'form' in context 0 does not contain the field 'some_field'") def test_noerror_field(self): "An assertion is raised if the field doesn't have any errors" @@ -327,6 +422,10 @@ class AssertFormErrorTests(TestCase): self.assertFormError(response, 'form', 'value', 'Some error.') except AssertionError, e: self.assertEqual(str(e), "The field 'value' on form 'form' in context 0 contains no errors") + try: + self.assertFormError(response, 'form', 'value', 'Some error.', msg_prefix='abc') + except AssertionError, e: + self.assertEqual(str(e), "abc: The field 'value' on form 'form' in context 0 contains no errors") def test_unknown_error(self): "An assertion is raised if the field doesn't contain the provided error" @@ -345,6 +444,10 @@ class AssertFormErrorTests(TestCase): self.assertFormError(response, 'form', 'email', 'Some error.') except AssertionError, e: self.assertEqual(str(e), "The field 'email' on form 'form' in context 0 does not contain the error 'Some error.' (actual errors: [u'Enter a valid e-mail address.'])") + try: + self.assertFormError(response, 'form', 'email', 'Some error.', msg_prefix='abc') + except AssertionError, e: + self.assertEqual(str(e), "abc: The field 'email' on form 'form' in context 0 does not contain the error 'Some error.' (actual errors: [u'Enter a valid e-mail address.'])") def test_unknown_nonfield_error(self): """ @@ -366,6 +469,10 @@ class AssertFormErrorTests(TestCase): self.assertFormError(response, 'form', None, 'Some error.') except AssertionError, e: self.assertEqual(str(e), "The form 'form' in context 0 does not contain the non-field error 'Some error.' (actual errors: )") + try: + self.assertFormError(response, 'form', None, 'Some error.', msg_prefix='abc') + except AssertionError, e: + self.assertEqual(str(e), "abc: The form 'form' in context 0 does not contain the non-field error 'Some error.' (actual errors: )") class LoginTests(TestCase): fixtures = ['testdata']