Jelajahi Sumber

Fixed #34657 -- Made assert(Not)Contains/assertInHTML display haystacks in error messages.

Chinmoy Chakraborty 1 tahun lalu
induk
melakukan
1dae65dc63

+ 29 - 12
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):

+ 4 - 1
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
 ~~~~

+ 12 - 0
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 <django.http.HttpResponse>` 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.

+ 69 - 14
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

+ 43 - 4
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 '<b>Hello</b>' in response"
+        msg = (
+            "False is not true : Couldn't find '<b>Hello</b>' in the following "
+            "response\n'<p>Test</p>'"
+        )
         with self.assertRaisesMessage(AssertionError, msg):
             self.assertInHTML("<b>Hello</b>", "<p>Test</p>")
 
     def test_msg_prefix(self):
-        msg = "False is not true : Prefix: Couldn't find '<b>Hello</b>' in response"
+        msg = (
+            "False is not true : Prefix: Couldn't find '<b>Hello</b>' in the following "
+            'response\n\'<input type="text" name="Hello" />\''
+        )
         with self.assertRaisesMessage(AssertionError, msg):
             self.assertInHTML(
                 "<b>Hello</b>",
@@ -1000,8 +1006,9 @@ class InHTMLTests(SimpleTestCase):
 
     def test_count_msg_prefix(self):
         msg = (
-            "2 != 1 : Prefix: Found 2 instances of '<b>Hello</b>' in response "
-            "(expected 1)"
+            "2 != 1 : Prefix: Found 2 instances of '<b>Hello</b>' (expected 1) in the "
+            "following response\n'<b>Hello</b><b>Hello</b>'"
+            ""
         )
         with self.assertRaisesMessage(AssertionError, msg):
             self.assertInHTML(
@@ -1011,6 +1018,38 @@ class InHTMLTests(SimpleTestCase):
                 msg_prefix="Prefix",
             )
 
+    def test_base(self):
+        haystack = "<p><b>Hello</b> <span>there</span>! Hi <span>there</span>!</p>"
+
+        self.assertInHTML("<b>Hello</b>", haystack=haystack)
+        msg = f"Couldn't find '<p>Howdy</p>' in the following response\n{haystack!r}"
+        with self.assertRaisesMessage(AssertionError, msg):
+            self.assertInHTML("<p>Howdy</p>", haystack)
+
+        self.assertInHTML("<span>there</span>", haystack=haystack, count=2)
+        msg = (
+            "Found 1 instances of '<b>Hello</b>' (expected 2) in the following response"
+            f"\n{haystack!r}"
+        )
+        with self.assertRaisesMessage(AssertionError, msg):
+            self.assertInHTML("<b>Hello</b>", haystack=haystack, count=2)
+
+    def test_long_haystack(self):
+        haystack = (
+            "<p>This is a very very very very very very very very long message which "
+            "exceedes the max limit of truncation.</p>"
+        )
+        msg = f"Couldn't find '<b>Hello</b>' in the following response\n{haystack!r}"
+        with self.assertRaisesMessage(AssertionError, msg):
+            self.assertInHTML("<b>Hello</b>", haystack)
+
+        msg = (
+            "Found 0 instances of '<b>This</b>' (expected 3) in the following response"
+            f"\n{haystack!r}"
+        )
+        with self.assertRaisesMessage(AssertionError, msg):
+            self.assertInHTML("<b>This</b>", haystack, 3)
+
 
 class JSONEqualTests(SimpleTestCase):
     def test_simple_equal(self):