diff --git a/django/test/testcases.py b/django/test/testcases.py index bdb64b3a155..9855c318be3 100644 --- a/django/test/testcases.py +++ b/django/test/testcases.py @@ -495,6 +495,7 @@ class SimpleTestCase(unittest.TestCase): content = b"".join(response.streaming_content) else: content = response.content + content_repr = safe_repr(content) if not isinstance(text, bytes) or html: text = str(text) content = content.decode(response.charset) @@ -509,7 +510,7 @@ class SimpleTestCase(unittest.TestCase): self, text, None, "Second argument is not valid HTML:" ) real_count = content.count(text) - return (text_repr, real_count, msg_prefix) + return text_repr, real_count, msg_prefix, content_repr def assertContains( self, response, text, count=None, status_code=200, msg_prefix="", html=False @@ -521,7 +522,7 @@ class SimpleTestCase(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. """ - text_repr, real_count, msg_prefix = self._assert_contains( + text_repr, real_count, msg_prefix, content_repr = self._assert_contains( response, text, status_code, msg_prefix, html ) @@ -529,13 +530,18 @@ class SimpleTestCase(unittest.TestCase): self.assertEqual( real_count, count, - msg_prefix - + "Found %d instances of %s in response (expected %d)" - % (real_count, text_repr, count), + ( + f"{msg_prefix}Found {real_count} instances of {text_repr} " + f"(expected {count}) in the following response\n{content_repr}" + ), ) else: self.assertTrue( - real_count != 0, msg_prefix + "Couldn't find %s in response" % text_repr + real_count != 0, + ( + f"{msg_prefix}Couldn't find {text_repr} in the following response\n" + f"{content_repr}" + ), ) def assertNotContains( @@ -546,12 +552,17 @@ class SimpleTestCase(unittest.TestCase): successfully, (i.e., the HTTP status code was as expected) and that ``text`` doesn't occur in the content of the response. """ - text_repr, real_count, msg_prefix = self._assert_contains( + text_repr, real_count, msg_prefix, content_repr = self._assert_contains( response, text, status_code, msg_prefix, html ) self.assertEqual( - real_count, 0, msg_prefix + "Response should not contain %s" % text_repr + real_count, + 0, + ( + f"{msg_prefix}{text_repr} unexpectedly found in the following response" + f"\n{content_repr}" + ), ) def _check_test_client_response(self, response, attribute, method_name): @@ -884,17 +895,23 @@ class SimpleTestCase(unittest.TestCase): real_count = parsed_haystack.count(parsed_needle) if msg_prefix: msg_prefix += ": " + haystack_repr = safe_repr(haystack) if count is not None: self.assertEqual( real_count, count, - msg_prefix - + "Found %d instances of '%s' in response (expected %d)" - % (real_count, needle, count), + ( + f"{msg_prefix}Found {real_count} instances of {needle!r} (expected " + f"{count}) in the following response\n{haystack_repr}" + ), ) else: self.assertTrue( - real_count != 0, msg_prefix + "Couldn't find '%s' in response" % needle + real_count != 0, + ( + f"{msg_prefix}Couldn't find {needle!r} in the following response\n" + f"{haystack_repr}" + ), ) def assertJSONEqual(self, raw, expected_data, msg=None): diff --git a/docs/releases/5.1.txt b/docs/releases/5.1.txt index a8f0c32f9b5..aa8fe80aace 100644 --- a/docs/releases/5.1.txt +++ b/docs/releases/5.1.txt @@ -201,7 +201,10 @@ Templates Tests ~~~~~ -* ... +* :meth:`~django.test.SimpleTestCase.assertContains`, + :meth:`~django.test.SimpleTestCase.assertNotContains`, and + :meth:`~django.test.SimpleTestCase.assertInHTML` assertions now add haystacks + to assertion error messages. URLs ~~~~ diff --git a/docs/topics/testing/tools.txt b/docs/topics/testing/tools.txt index 16cc7759b35..a171941a854 100644 --- a/docs/topics/testing/tools.txt +++ b/docs/topics/testing/tools.txt @@ -1700,6 +1700,10 @@ your test suite. attribute ordering is not significant. See :meth:`~SimpleTestCase.assertHTMLEqual` for more details. + .. versionchanged:: 5.1 + + In older versions, error messages didn't contain the response content. + .. method:: SimpleTestCase.assertNotContains(response, text, status_code=200, msg_prefix='', html=False) Asserts that a :class:`response ` produced the @@ -1712,6 +1716,10 @@ your test suite. attribute ordering is not significant. See :meth:`~SimpleTestCase.assertHTMLEqual` for more details. + .. versionchanged:: 5.1 + + In older versions, error messages didn't contain the response content. + .. method:: SimpleTestCase.assertTemplateUsed(response, template_name, msg_prefix='', count=None) Asserts that the template with the given name was used in rendering the @@ -1848,6 +1856,10 @@ your test suite. Whitespace in most cases is ignored, and attribute ordering is not significant. See :meth:`~SimpleTestCase.assertHTMLEqual` for more details. + .. versionchanged:: 5.1 + + In older versions, error messages didn't contain the ``haystack``. + .. method:: SimpleTestCase.assertJSONEqual(raw, expected_data, msg=None) Asserts that the JSON fragments ``raw`` and ``expected_data`` are equal. diff --git a/tests/test_client_regress/tests.py b/tests/test_client_regress/tests.py index 726d1dcf8f1..d5b9807f4da 100644 --- a/tests/test_client_regress/tests.py +++ b/tests/test_client_regress/tests.py @@ -80,86 +80,141 @@ class AssertContainsTests(SimpleTestCase): try: self.assertNotContains(response, "once") except AssertionError as e: - self.assertIn("Response should not contain 'once'", str(e)) + self.assertIn( + "'once' unexpectedly found in the following response\n" + f"{response.content}", + str(e), + ) try: self.assertNotContains(response, "once", msg_prefix="abc") except AssertionError as e: - self.assertIn("abc: Response should not contain 'once'", str(e)) + self.assertIn( + "abc: 'once' unexpectedly found in the following response\n" + f"{response.content}", + str(e), + ) try: self.assertContains(response, "never", 1) except AssertionError as e: self.assertIn( - "Found 0 instances of 'never' in response (expected 1)", str(e) + "Found 0 instances of 'never' (expected 1) in the following response\n" + f"{response.content}", + str(e), ) try: self.assertContains(response, "never", 1, msg_prefix="abc") except AssertionError as e: self.assertIn( - "abc: Found 0 instances of 'never' in response (expected 1)", str(e) + "abc: Found 0 instances of 'never' (expected 1) in the following " + f"response\n{response.content}", + str(e), ) try: self.assertContains(response, "once", 0) except AssertionError as e: self.assertIn( - "Found 1 instances of 'once' in response (expected 0)", str(e) + "Found 1 instances of 'once' (expected 0) in the following response\n" + f"{response.content}", + str(e), ) try: self.assertContains(response, "once", 0, msg_prefix="abc") except AssertionError as e: self.assertIn( - "abc: Found 1 instances of 'once' in response (expected 0)", str(e) + "abc: Found 1 instances of 'once' (expected 0) in the following " + f"response\n{response.content}", + str(e), ) try: self.assertContains(response, "once", 2) except AssertionError as e: self.assertIn( - "Found 1 instances of 'once' in response (expected 2)", str(e) + "Found 1 instances of 'once' (expected 2) in the following response\n" + f"{response.content}", + str(e), ) try: self.assertContains(response, "once", 2, msg_prefix="abc") except AssertionError as e: self.assertIn( - "abc: Found 1 instances of 'once' in response (expected 2)", str(e) + "abc: Found 1 instances of 'once' (expected 2) in the following " + f"response\n{response.content}", + str(e), ) try: self.assertContains(response, "twice", 1) except AssertionError as e: self.assertIn( - "Found 2 instances of 'twice' in response (expected 1)", str(e) + "Found 2 instances of 'twice' (expected 1) in the following response\n" + f"{response.content}", + str(e), ) try: self.assertContains(response, "twice", 1, msg_prefix="abc") except AssertionError as e: self.assertIn( - "abc: Found 2 instances of 'twice' in response (expected 1)", str(e) + "abc: Found 2 instances of 'twice' (expected 1) in the following " + f"response\n{response.content}", + str(e), ) try: self.assertContains(response, "thrice") except AssertionError as e: - self.assertIn("Couldn't find 'thrice' in response", str(e)) + self.assertIn( + f"Couldn't find 'thrice' in the following response\n{response.content}", + str(e), + ) try: self.assertContains(response, "thrice", msg_prefix="abc") except AssertionError as e: - self.assertIn("abc: Couldn't find 'thrice' in response", str(e)) + self.assertIn( + "abc: Couldn't find 'thrice' in the following response\n" + f"{response.content}", + str(e), + ) try: self.assertContains(response, "thrice", 3) except AssertionError as e: self.assertIn( - "Found 0 instances of 'thrice' in response (expected 3)", str(e) + "Found 0 instances of 'thrice' (expected 3) in the following response\n" + f"{response.content}", + str(e), ) try: self.assertContains(response, "thrice", 3, msg_prefix="abc") except AssertionError as e: self.assertIn( - "abc: Found 0 instances of 'thrice' in response (expected 3)", str(e) + "abc: Found 0 instances of 'thrice' (expected 3) in the following " + f"response\n{response.content}", + str(e), ) + long_content = ( + b"This is a very very very very very very very very long message which " + b"exceedes the max limit of truncation." + ) + response = HttpResponse(long_content) + msg = f"Couldn't find 'thrice' in the following response\n{long_content}" + with self.assertRaisesMessage(AssertionError, msg): + self.assertContains(response, "thrice") + + msg = ( + "Found 1 instances of 'This' (expected 3) in the following response\n" + f"{long_content}" + ) + with self.assertRaisesMessage(AssertionError, msg): + self.assertContains(response, "This", 3) + + msg = f"'very' unexpectedly found in the following response\n{long_content}" + with self.assertRaisesMessage(AssertionError, msg): + self.assertNotContains(response, "very") + def test_unicode_contains(self): "Unicode characters can be found in template context" # Regression test for #10183 diff --git a/tests/test_utils/tests.py b/tests/test_utils/tests.py index 95526f768f9..7df81650d58 100644 --- a/tests/test_utils/tests.py +++ b/tests/test_utils/tests.py @@ -985,12 +985,18 @@ class HTMLEqualTests(SimpleTestCase): class InHTMLTests(SimpleTestCase): def test_needle_msg(self): - msg = "False is not true : Couldn't find 'Hello' in response" + msg = ( + "False is not true : Couldn't find 'Hello' in the following " + "response\n'

Test

'" + ) with self.assertRaisesMessage(AssertionError, msg): self.assertInHTML("Hello", "

Test

") def test_msg_prefix(self): - msg = "False is not true : Prefix: Couldn't find 'Hello' in response" + msg = ( + "False is not true : Prefix: Couldn't find 'Hello' in the following " + 'response\n\'\'' + ) with self.assertRaisesMessage(AssertionError, msg): self.assertInHTML( "Hello", @@ -1000,8 +1006,9 @@ class InHTMLTests(SimpleTestCase): def test_count_msg_prefix(self): msg = ( - "2 != 1 : Prefix: Found 2 instances of 'Hello' in response " - "(expected 1)" + "2 != 1 : Prefix: Found 2 instances of 'Hello' (expected 1) in the " + "following response\n'HelloHello'" + "" ) with self.assertRaisesMessage(AssertionError, msg): self.assertInHTML( @@ -1011,6 +1018,38 @@ class InHTMLTests(SimpleTestCase): msg_prefix="Prefix", ) + def test_base(self): + haystack = "

Hello there! Hi there!

" + + self.assertInHTML("Hello", haystack=haystack) + msg = f"Couldn't find '

Howdy

' in the following response\n{haystack!r}" + with self.assertRaisesMessage(AssertionError, msg): + self.assertInHTML("

Howdy

", haystack) + + self.assertInHTML("there", haystack=haystack, count=2) + msg = ( + "Found 1 instances of 'Hello' (expected 2) in the following response" + f"\n{haystack!r}" + ) + with self.assertRaisesMessage(AssertionError, msg): + self.assertInHTML("Hello", haystack=haystack, count=2) + + def test_long_haystack(self): + haystack = ( + "

This is a very very very very very very very very long message which " + "exceedes the max limit of truncation.

" + ) + msg = f"Couldn't find 'Hello' in the following response\n{haystack!r}" + with self.assertRaisesMessage(AssertionError, msg): + self.assertInHTML("Hello", haystack) + + msg = ( + "Found 0 instances of 'This' (expected 3) in the following response" + f"\n{haystack!r}" + ) + with self.assertRaisesMessage(AssertionError, msg): + self.assertInHTML("This", haystack, 3) + class JSONEqualTests(SimpleTestCase): def test_simple_equal(self):