How can I unit test django messages?
Solution 1
I found a really easy approach:
response = self.client.post('/foo/')
messages = list(response.context['messages'])
self.assertEqual(len(messages), 1)
self.assertEqual(str(messages[0]), 'my message')
If you need to check for messages on a response that has no context you can use the following:
from django.contrib.messages import get_messages
messages = list(get_messages(response.wsgi_request))
self.assertEqual(len(messages), 1)
self.assertEqual(str(messages[0]), 'my message')
The fallback storage doesn't support indexing, however it is an iterable.
Solution 2
From django documentation:
Outside of templates, you can use get_messages()
So, you could write something like:
from django.contrib.messages import get_messages
[...]
messages = [m.message for m in get_messages(response.wsgi_request)]
self.assertIn('My message', messages)
Solution 3
This works for me (displays all messages):
print [m.message for m in list(response.context['messages'])]
Also here are a couple of utility methods I have in a test class inherited from Django's TestCase. If you'd prefer to have them as functions, remove the self
arguments and replace self.fail()
's with a raise
.
def assert_message_count(self, response, expect_num):
"""
Asserts that exactly the given number of messages have been sent.
"""
actual_num = len(response.context['messages'])
if actual_num != expect_num:
self.fail('Message count was %d, expected %d' %
(actual_num, expect_num))
def assert_message_contains(self, response, text, level=None):
"""
Asserts that there is exactly one message containing the given text.
"""
messages = response.context['messages']
matches = [m for m in messages if text in m.message]
if len(matches) == 1:
msg = matches[0]
if level is not None and msg.level != level:
self.fail('There was one matching message but with different'
'level: %s != %s' % (msg.level, level))
return
elif len(matches) == 0:
messages_str = ", ".join('"%s"' % m for m in messages)
self.fail('No message contained text "%s", messages were: %s' %
(text, messages_str))
else:
self.fail('Multiple messages contained text "%s": %s' %
(text, ", ".join(('"%s"' % m) for m in matches)))
def assert_message_not_contains(self, response, text):
""" Assert that no message contains the given text. """
messages = response.context['messages']
matches = [m for m in messages if text in m.message]
if len(matches) > 0:
self.fail('Message(s) contained text "%s": %s' %
(text, ", ".join(('"%s"' % m) for m in matches)))
Solution 4
Update
My original answer was written when django was still 1.1 or so. This answer is no longer relevant. See @daveoncode's answer for a better solution.
Original Answer
I did an experiment to test this. I changed the MESSAGE_STORAGE
setting in one of my projects to 'django.contrib.messages.storage.cookie.CookieStorage'
and executed a test that I had written to check for messages. It worked.
The key difference from what you were doing is the way I retrieved messages. See below:
def test_message_sending(self):
data = dict(...)
response = self.client.post(reverse('my_view'), data)
messages = self.user.get_and_delete_messages()
self.assertTrue(messages)
self.assertEqual('Hey there!', messages[0])
This may be worth a try.
dvydra
Updated on July 08, 2022Comments
-
dvydra almost 2 years
In my django application, I'm trying to write a unit test that performs an action and then checks the messages in the response.
As far as I can tell, there is no nice way of doing this.
I'm using the CookieStorage storage method, and I'd like to do something similar to the following:
response = self.client.post('/do-something/', follow=True) self.assertEquals(response.context['messages'][0], "fail.")
The problem is, all I get back is a
print response.context['messages'] <django.contrib.messages.storage.cookie.CookieStorage object at 0x3c55250>
How can I turn this into something useful, or am I doing it all wrong?
Thanks, Daniel
-
pkoch over 12 yearsOnly works with explicit ResponseContext or TemplateResponse (which tries to construct a ResponseContext).
-
Dave over 12 yearsuser.get_and_delete_messages() was deprecated in Django 1.2
-
anttikoo almost 12 yearsThis is not exactly the same, as my version checks that the given text is contained in (not equal to) any/none of the messages. I prefer it that way, so that I only have to enter the key part of the message in a testcase and allow the message text to be updated without breaking the test.
-
Aaron Lelevier over 9 yearsI also found that
self.assertEqual(m[0].message, 'my message')
works -
BenjaminGolder over 7 yearsif you need to check for messages on a response that has no context (such as a redirect), you can use
list(r.wsgi_request._messages)
-
Dunatotatos about 7 yearsIt looks like [0] does not work on the new versions :
*** TypeError: 'FallbackStorage' object does not support indexing
. However, it is an Iterable and you can use anyfor m in messages
construct. -
Jonathan almost 7 yearsNice one @BenjaminGolder where did you find that? Digging into the source code?
-
BenjaminGolder almost 7 years@Jonathan by using
dir()
in inside an interactive debugger (import ipdb; ipdb.set_trace()
) inside a test case. -
OrangeDog about 6 years
-
OrangeDog about 6 yearsThe way to do it without touching private attributes is
get_messages(response.wsgi_request)
-
FiddleStix over 4 years
messages = [str(message) for message in get_messages(post_response.wsgi_request)]
self.assertListEqual(["some message."], messages)
may save a few chars