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

This commit is contained in:
Chinmoy Chakraborty 2023-10-02 23:16:21 +05:30 committed by Mariusz Felisiak
parent 54d9d26ebf
commit 1dae65dc63
5 changed files with 157 additions and 31 deletions

View File

@ -495,6 +495,7 @@ class SimpleTestCase(unittest.TestCase):
content = b"".join(response.streaming_content) content = b"".join(response.streaming_content)
else: else:
content = response.content content = response.content
content_repr = safe_repr(content)
if not isinstance(text, bytes) or html: if not isinstance(text, bytes) or html:
text = str(text) text = str(text)
content = content.decode(response.charset) content = content.decode(response.charset)
@ -509,7 +510,7 @@ class SimpleTestCase(unittest.TestCase):
self, text, None, "Second argument is not valid HTML:" self, text, None, "Second argument is not valid HTML:"
) )
real_count = content.count(text) real_count = content.count(text)
return (text_repr, real_count, msg_prefix) return text_repr, real_count, msg_prefix, content_repr
def assertContains( def assertContains(
self, response, text, count=None, status_code=200, msg_prefix="", html=False 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 ``count`` is None, the count doesn't matter - the assertion is true
if the text occurs at least once in the response. 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 response, text, status_code, msg_prefix, html
) )
@ -529,13 +530,18 @@ class SimpleTestCase(unittest.TestCase):
self.assertEqual( self.assertEqual(
real_count, real_count,
count, count,
msg_prefix (
+ "Found %d instances of %s in response (expected %d)" f"{msg_prefix}Found {real_count} instances of {text_repr} "
% (real_count, text_repr, count), f"(expected {count}) in the following response\n{content_repr}"
),
) )
else: else:
self.assertTrue( 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( def assertNotContains(
@ -546,12 +552,17 @@ class SimpleTestCase(unittest.TestCase):
successfully, (i.e., the HTTP status code was as expected) and that successfully, (i.e., the HTTP status code was as expected) and that
``text`` doesn't occur in the content of the response. ``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 response, text, status_code, msg_prefix, html
) )
self.assertEqual( 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): 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) real_count = parsed_haystack.count(parsed_needle)
if msg_prefix: if msg_prefix:
msg_prefix += ": " msg_prefix += ": "
haystack_repr = safe_repr(haystack)
if count is not None: if count is not None:
self.assertEqual( self.assertEqual(
real_count, real_count,
count, count,
msg_prefix (
+ "Found %d instances of '%s' in response (expected %d)" f"{msg_prefix}Found {real_count} instances of {needle!r} (expected "
% (real_count, needle, count), f"{count}) in the following response\n{haystack_repr}"
),
) )
else: else:
self.assertTrue( 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): def assertJSONEqual(self, raw, expected_data, msg=None):

View File

@ -201,7 +201,10 @@ Templates
Tests 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 URLs
~~~~ ~~~~

View File

@ -1700,6 +1700,10 @@ your test suite.
attribute ordering is not significant. See attribute ordering is not significant. See
:meth:`~SimpleTestCase.assertHTMLEqual` for more details. :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) .. method:: SimpleTestCase.assertNotContains(response, text, status_code=200, msg_prefix='', html=False)
Asserts that a :class:`response <django.http.HttpResponse>` produced the Asserts that a :class:`response <django.http.HttpResponse>` produced the
@ -1712,6 +1716,10 @@ your test suite.
attribute ordering is not significant. See attribute ordering is not significant. See
:meth:`~SimpleTestCase.assertHTMLEqual` for more details. :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) .. method:: SimpleTestCase.assertTemplateUsed(response, template_name, msg_prefix='', count=None)
Asserts that the template with the given name was used in rendering the 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 Whitespace in most cases is ignored, and attribute ordering is not
significant. See :meth:`~SimpleTestCase.assertHTMLEqual` for more details. 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) .. method:: SimpleTestCase.assertJSONEqual(raw, expected_data, msg=None)
Asserts that the JSON fragments ``raw`` and ``expected_data`` are equal. Asserts that the JSON fragments ``raw`` and ``expected_data`` are equal.

View File

@ -80,86 +80,141 @@ class AssertContainsTests(SimpleTestCase):
try: try:
self.assertNotContains(response, "once") self.assertNotContains(response, "once")
except AssertionError as e: 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: try:
self.assertNotContains(response, "once", msg_prefix="abc") self.assertNotContains(response, "once", msg_prefix="abc")
except AssertionError as e: 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: try:
self.assertContains(response, "never", 1) self.assertContains(response, "never", 1)
except AssertionError as e: except AssertionError as e:
self.assertIn( 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: try:
self.assertContains(response, "never", 1, msg_prefix="abc") self.assertContains(response, "never", 1, msg_prefix="abc")
except AssertionError as e: except AssertionError as e:
self.assertIn( 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: try:
self.assertContains(response, "once", 0) self.assertContains(response, "once", 0)
except AssertionError as e: except AssertionError as e:
self.assertIn( 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: try:
self.assertContains(response, "once", 0, msg_prefix="abc") self.assertContains(response, "once", 0, msg_prefix="abc")
except AssertionError as e: except AssertionError as e:
self.assertIn( 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: try:
self.assertContains(response, "once", 2) self.assertContains(response, "once", 2)
except AssertionError as e: except AssertionError as e:
self.assertIn( 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: try:
self.assertContains(response, "once", 2, msg_prefix="abc") self.assertContains(response, "once", 2, msg_prefix="abc")
except AssertionError as e: except AssertionError as e:
self.assertIn( 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: try:
self.assertContains(response, "twice", 1) self.assertContains(response, "twice", 1)
except AssertionError as e: except AssertionError as e:
self.assertIn( 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: try:
self.assertContains(response, "twice", 1, msg_prefix="abc") self.assertContains(response, "twice", 1, msg_prefix="abc")
except AssertionError as e: except AssertionError as e:
self.assertIn( 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: try:
self.assertContains(response, "thrice") self.assertContains(response, "thrice")
except AssertionError as e: 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: try:
self.assertContains(response, "thrice", msg_prefix="abc") self.assertContains(response, "thrice", msg_prefix="abc")
except AssertionError as e: 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: try:
self.assertContains(response, "thrice", 3) self.assertContains(response, "thrice", 3)
except AssertionError as e: except AssertionError as e:
self.assertIn( 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: try:
self.assertContains(response, "thrice", 3, msg_prefix="abc") self.assertContains(response, "thrice", 3, msg_prefix="abc")
except AssertionError as e: except AssertionError as e:
self.assertIn( 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): def test_unicode_contains(self):
"Unicode characters can be found in template context" "Unicode characters can be found in template context"
# Regression test for #10183 # Regression test for #10183

View File

@ -985,12 +985,18 @@ class HTMLEqualTests(SimpleTestCase):
class InHTMLTests(SimpleTestCase): class InHTMLTests(SimpleTestCase):
def test_needle_msg(self): 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): with self.assertRaisesMessage(AssertionError, msg):
self.assertInHTML("<b>Hello</b>", "<p>Test</p>") self.assertInHTML("<b>Hello</b>", "<p>Test</p>")
def test_msg_prefix(self): 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): with self.assertRaisesMessage(AssertionError, msg):
self.assertInHTML( self.assertInHTML(
"<b>Hello</b>", "<b>Hello</b>",
@ -1000,8 +1006,9 @@ class InHTMLTests(SimpleTestCase):
def test_count_msg_prefix(self): def test_count_msg_prefix(self):
msg = ( msg = (
"2 != 1 : Prefix: Found 2 instances of '<b>Hello</b>' in response " "2 != 1 : Prefix: Found 2 instances of '<b>Hello</b>' (expected 1) in the "
"(expected 1)" "following response\n'<b>Hello</b><b>Hello</b>'"
""
) )
with self.assertRaisesMessage(AssertionError, msg): with self.assertRaisesMessage(AssertionError, msg):
self.assertInHTML( self.assertInHTML(
@ -1011,6 +1018,38 @@ class InHTMLTests(SimpleTestCase):
msg_prefix="Prefix", 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): class JSONEqualTests(SimpleTestCase):
def test_simple_equal(self): def test_simple_equal(self):