From cafe7266ee69f7e017ddbc0d440084ace559b04b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Freitag?= <mail@franek.fr> Date: Thu, 15 Jun 2023 17:16:46 +0200 Subject: [PATCH] Fixed #34730 -- Added django.contrib.messages.test.MessagesTestMixin.assertMessages(). --- django/contrib/messages/test.py | 8 +++ docs/ref/contrib/messages.txt | 33 ++++++++++ docs/releases/5.0.txt | 4 +- tests/messages_tests/test_api.py | 15 +---- tests/messages_tests/tests.py | 102 ++++++++++++++++++++++++++++++- tests/messages_tests/utils.py | 14 +++++ 6 files changed, 161 insertions(+), 15 deletions(-) create mode 100644 django/contrib/messages/test.py create mode 100644 tests/messages_tests/utils.py diff --git a/django/contrib/messages/test.py b/django/contrib/messages/test.py new file mode 100644 index 0000000000..3a69f54585 --- /dev/null +++ b/django/contrib/messages/test.py @@ -0,0 +1,8 @@ +from .api import get_messages + + +class MessagesTestMixin: + def assertMessages(self, response, expected_messages, *, ordered=True): + request_messages = list(get_messages(response.wsgi_request)) + assertion = self.assertEqual if ordered else self.assertCountEqual + assertion(request_messages, expected_messages) diff --git a/docs/ref/contrib/messages.txt b/docs/ref/contrib/messages.txt index f5c5621c71..b85f425277 100644 --- a/docs/ref/contrib/messages.txt +++ b/docs/ref/contrib/messages.txt @@ -452,3 +452,36 @@ the session cookie settings: * :setting:`SESSION_COOKIE_DOMAIN` * :setting:`SESSION_COOKIE_SECURE` * :setting:`SESSION_COOKIE_HTTPONLY` + +Testing +======= + +.. versionadded:: 5.0 + +This module offers a tailored test assertion method, for testing messages +attached to an :class:`~.HttpResponse`. + +To benefit from this assertion, add ``MessagesTestMixin`` to the class +hierarchy:: + + from django.contrib.messages.test import MessagesTestMixin + from django.test import TestCase + + + class MsgTestCase(MessagesTestMixin, TestCase): + pass + +Then, inherit from the ``MsgTestCase`` in your tests. + +.. module:: django.contrib.messages.test + +.. method:: MessagesTestMixin.assertMessages(response, expected_messages, ordered=True) + + Asserts that :mod:`~django.contrib.messages` added to the :class:`response + <django.http.HttpResponse>` matches ``expected_messages``. + + ``expected_messages`` is a list of + :class:`~django.contrib.messages.Message` objects. + + By default, the comparison is ordering dependent. You can disable this by + setting the ``ordered`` argument to ``False``. diff --git a/docs/releases/5.0.txt b/docs/releases/5.0.txt index 5a8e44f1fb..5b8abc8498 100644 --- a/docs/releases/5.0.txt +++ b/docs/releases/5.0.txt @@ -247,7 +247,9 @@ Minor features :mod:`django.contrib.messages` ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -* ... +* The new :meth:`.MessagesTestMixin.assertMessages` assertion method allows + testing :mod:`~django.contrib.messages` added to a + :class:`response <django.http.HttpResponse>`. :mod:`django.contrib.postgres` ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/tests/messages_tests/test_api.py b/tests/messages_tests/test_api.py index 40283d4153..802f5b53ce 100644 --- a/tests/messages_tests/test_api.py +++ b/tests/messages_tests/test_api.py @@ -1,17 +1,7 @@ from django.contrib import messages from django.test import RequestFactory, SimpleTestCase - -class DummyStorage: - """ - dummy message-store to test the api methods - """ - - def __init__(self): - self.store = [] - - def add(self, level, message, extra_tags=""): - self.store.append(message) +from .utils import DummyStorage class ApiTests(SimpleTestCase): @@ -25,7 +15,8 @@ class ApiTests(SimpleTestCase): msg = "some message" self.request._messages = self.storage messages.add_message(self.request, messages.DEBUG, msg) - self.assertIn(msg, self.storage.store) + [message] = self.storage.store + self.assertEqual(msg, message.message) def test_request_is_none(self): msg = "add_message() argument must be an HttpRequest object, not 'NoneType'." diff --git a/tests/messages_tests/tests.py b/tests/messages_tests/tests.py index 1ebb6aaba2..4280cb6e3b 100644 --- a/tests/messages_tests/tests.py +++ b/tests/messages_tests/tests.py @@ -1,8 +1,11 @@ from unittest import mock -from django.contrib.messages import Message, constants +from django.contrib.messages import Message, add_message, constants from django.contrib.messages.storage import base -from django.test import SimpleTestCase, override_settings +from django.contrib.messages.test import MessagesTestMixin +from django.test import RequestFactory, SimpleTestCase, override_settings + +from .utils import DummyStorage class MessageTests(SimpleTestCase): @@ -59,3 +62,98 @@ class TestLevelTags(SimpleTestCase): @override_settings(MESSAGE_TAGS=message_tags) def test_override_settings_level_tags(self): self.assertEqual(base.LEVEL_TAGS, self.message_tags) + + +class FakeResponse: + def __init__(self): + request = RequestFactory().get("/") + request._messages = DummyStorage() + self.wsgi_request = request + + +class AssertMessagesTest(MessagesTestMixin, SimpleTestCase): + def test_assertion(self): + response = FakeResponse() + add_message(response.wsgi_request, constants.DEBUG, "DEBUG message.") + add_message(response.wsgi_request, constants.INFO, "INFO message.") + add_message(response.wsgi_request, constants.SUCCESS, "SUCCESS message.") + add_message(response.wsgi_request, constants.WARNING, "WARNING message.") + add_message(response.wsgi_request, constants.ERROR, "ERROR message.") + self.assertMessages( + response, + [ + Message(constants.DEBUG, "DEBUG message."), + Message(constants.INFO, "INFO message."), + Message(constants.SUCCESS, "SUCCESS message."), + Message(constants.WARNING, "WARNING message."), + Message(constants.ERROR, "ERROR message."), + ], + ) + + def test_with_tags(self): + response = FakeResponse() + add_message( + response.wsgi_request, + constants.INFO, + "INFO message.", + extra_tags="extra-info", + ) + add_message( + response.wsgi_request, + constants.SUCCESS, + "SUCCESS message.", + extra_tags="extra-success", + ) + add_message( + response.wsgi_request, + constants.WARNING, + "WARNING message.", + extra_tags="extra-warning", + ) + add_message( + response.wsgi_request, + constants.ERROR, + "ERROR message.", + extra_tags="extra-error", + ) + self.assertMessages( + response, + [ + Message(constants.INFO, "INFO message.", "extra-info"), + Message(constants.SUCCESS, "SUCCESS message.", "extra-success"), + Message(constants.WARNING, "WARNING message.", "extra-warning"), + Message(constants.ERROR, "ERROR message.", "extra-error"), + ], + ) + + @override_settings(MESSAGE_TAGS={42: "CUSTOM"}) + def test_custom_levelname(self): + response = FakeResponse() + add_message(response.wsgi_request, 42, "CUSTOM message.") + self.assertMessages(response, [Message(42, "CUSTOM message.")]) + + def test_ordered(self): + response = FakeResponse() + add_message(response.wsgi_request, constants.INFO, "First message.") + add_message(response.wsgi_request, constants.WARNING, "Second message.") + expected_messages = [ + Message(constants.WARNING, "Second message."), + Message(constants.INFO, "First message."), + ] + self.assertMessages(response, expected_messages, ordered=False) + with self.assertRaisesMessage(AssertionError, "Lists differ: "): + self.assertMessages(response, expected_messages) + + def test_mismatching_length(self): + response = FakeResponse() + add_message(response.wsgi_request, constants.INFO, "INFO message.") + msg = ( + "Lists differ: [Message(level=20, message='INFO message.')] != []\n\n" + "First list contains 1 additional elements.\n" + "First extra element 0:\n" + "Message(level=20, message='INFO message.')\n\n" + "- [Message(level=20, message='INFO message.')]\n" + "+ []" + ) + with self.assertRaisesMessage(AssertionError, msg): + self.assertMessages(response, []) diff --git a/tests/messages_tests/utils.py b/tests/messages_tests/utils.py new file mode 100644 index 0000000000..f00ea4585b --- /dev/null +++ b/tests/messages_tests/utils.py @@ -0,0 +1,14 @@ +from django.contrib.messages import Message + + +class DummyStorage: + """Dummy message-store to test the API methods.""" + + def __init__(self): + self.store = [] + + def add(self, level, message, extra_tags=""): + self.store.append(Message(level, message, extra_tags)) + + def __iter__(self): + return iter(self.store)