81

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

Manoj Govindan
  • 64,355
  • 21
  • 123
  • 132
dvydra
  • 925
  • 1
  • 6
  • 6
  • this works, but... seriously? response.context['messages']._get()[0][0].__dict__['message'] – dvydra May 24 '10 at 14:16
  • 5
    You can try to encapsulate this not so beautiful code in beautiful function `assert_has_message(response, msg_text)` and use it everywhere you would like after that. If you'll find better way to access the messages, you'll just modify the function in one place. – nkrkv May 24 '10 at 18:59
  • @nailxx, yeah, that's basically what I've done, but it's making me feel unwell :) – dvydra May 24 '10 at 23:11
  • 3
    This also works: `messages_list = CookieStorage(response)._decode(response.cookies['messages'].value)` This gives you a list of django.contrib.messages.storage.base.Message objects. – dvydra May 25 '10 at 09:58
  • @dvydra if you're still around, you may want to change the accepted answer – OrangeDog May 04 '18 at 15:07

6 Answers6

98

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.

OrangeDog
  • 30,151
  • 11
  • 105
  • 177
daveoncode
  • 16,281
  • 13
  • 90
  • 146
  • 2
    I also found that `self.assertEqual(m[0].message, 'my message')` works – Aaron Lelevier Aug 22 '14 at 14:12
  • 6
    if 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)` – BenjaminGolder Jan 19 '17 at 11:10
  • 6
    It 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 any `for m in messages` construct. – Dunatotatos Mar 19 '17 at 11:06
  • Nice one @BenjaminGolder where did you find that? Digging into the source code? – Jonathan Jul 20 '17 at 12:50
  • 2
    @Jonathan by using `dir()` in inside an interactive debugger (`import ipdb; ipdb.set_trace()`) inside a test case. – BenjaminGolder Jul 25 '17 at 17:23
  • 1
    @Jonathan https://docs.djangoproject.com/en/2.0/_modules/django/contrib/messages/middleware – OrangeDog May 04 '18 at 13:00
  • The way to do it without touching private attributes is `get_messages(response.wsgi_request)` – OrangeDog May 04 '18 at 15:02
  • 1
    `messages = [str(message) for message in get_messages(post_response.wsgi_request)]` `self.assertListEqual(["some message."], messages)` may save a few chars – FiddleStix Feb 14 '20 at 13:56
21

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)

powlo
  • 2,186
  • 2
  • 25
  • 34
moppag
  • 646
  • 5
  • 16
17

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)))
anttikoo
  • 686
  • 6
  • 12
  • 2
    Only works with explicit ResponseContext or TemplateResponse (which tries to construct a ResponseContext). – pkoch Oct 24 '11 at 20:24
3

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.

Manoj Govindan
  • 64,355
  • 21
  • 123
  • 132
0

Simpler version of the stalemate one:

class TestCaseMessagesMixture(object):
    def assertMessageCount(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 assertMessageEqual(self, response, text):
        """
        Asserts that the response includes the message text.
        """

        messages = [m.message for m in response.context['messages']]

        if text not in messages:
            self.fail(
                'No message with text "%s", messages were: %s' % 
                    (text, messages)
                )

    def assertMessageNotEqual(self, response, text):
        """
        Asserts that the response does not include the message text.
        """

        messages = [m.message for m in response.context['messages']]

        if text in messages:
            self.fail(
                'Message with text "%s" found, messages were: %s' % 
                    (text, messages)
                )
Marco Fucci
  • 1,806
  • 1
  • 18
  • 20
  • This 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. – anttikoo Jun 11 '12 at 12:00
0

Test helpers for validation of response messages count and content

def get_response_messages(self, response):
    from django.contrib.messages import get_messages
    return list(get_messages(response.wsgi_request))


def check_response_messages(self, response, message_index=None, message_value=None, exp_count=None):
    messages = self.get_response_messages(response)
    if exp_count is not None:
        self.assertEqual(len(messages), exp_count)

    if message_index is not None:
        message = messages[message_index]
        self.assertIn(message_value, str(message))

Can be used like this

message_value = "You can not switch to another type of account"
self.check_response_messages(response, exp_count=1, message_index=0, message_value=message_value)
pymen
  • 3,591
  • 34
  • 27