如何对 django 消息进行单元测试?

在我的 django 应用程序中,我尝试编写一个执行操作的单元测试,然后检查响应中的消息。

据我所知,没有什么好办法可以做到这一点。

我正在使用 CookieStorage 存储方法,我想做一些类似于下面的事情:

    response = self.client.post('/do-something/', follow=True)
self.assertEquals(response.context['messages'][0], "fail.")

问题是,我得到的只是

print response.context['messages']
<django.contrib.messages.storage.cookie.CookieStorage object at 0x3c55250>

我怎样才能把它变成有用的东西,还是我做错了?

谢谢, 丹尼尔

24274 次浏览

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.

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)))

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)
)

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.

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)

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)

I created a python class to simplify messages testing :

class TestMessageCase(TestCase):
""" class inherited from TestCase to add a function to test messages response """


def assertMessageContains(self, response, messages_list, debug=False):
"""
Function to test messages returned by the response view


:param response: The response of the view
:param messages_list: An ordered list of messages to test
:type messages_list: list of string
:param debug: Show all response messages
:type debug: bool
"""


response_messages = list(response.context['messages'])


if debug:
print(
" ---------------------------------------------------------------------\n",
"|              DEBUG MESSAGES RETURNED BY THE RESPONSE              |\n",
"---------------------------------------------------------------------"
)
for i in range(len(response_messages)):
print(f"Message n°{i + 1} :\n{response_messages[i]}\n\n")
print(
" ---------------------------------------------------------------------\n",
"|                             END DEBUG                             |\n",
"---------------------------------------------------------------------"
)


self.assertEqual(len(response_messages), len(messages_list))
for i in range(len(response_messages)):
self.assertEqual(str(response_messages[i]), messages_list[i])

And in test function :

response = self.client.post('/foo/')
self.assertMessageContains(response, ["Foo"])