我该如何对Django消息进行单元测试?

93

在我的 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>

我该如何将这个转化为有用的东西,还是我做错了什么?

谢谢, Daniel


这个可以工作,但是... 真的吗? - dvydra
5
你可以尝试将这段不太美观的代码封装在一个美丽的函数"assert_has_message(response, msg_text)"中,并在之后的任何地方都可以使用它。如果你找到更好的访问消息的方法,只需在一个地方修改函数即可。 - nkrkv
@nailxx,是的,基本上我已经这样做了,但这让我感觉不舒服 :) - dvydra
3
这也是可行的:messages_list = CookieStorage(response)._decode(response.cookies['messages'].value)。这将给您一个包含django.contrib.messages.storage.base.Message对象的列表。 - dvydra
@dvydra 如果你还在的话,你可能想要更改已接受的答案。 - OrangeDog
7个回答

131

我发现一种非常简单的方法:

response = self.client.post('/foo/')
messages = list(response.context['messages'])
self.assertEqual(len(messages), 1)
self.assertEqual(str(messages[0]), 'my message')

如果您需要检查没有上下文的响应中是否存在消息,可以使用以下方法:

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

备用存储不支持索引,但它是可迭代的。


2
我还发现self.assertEqual(m[0].message, 'my message')也可以生效。 - Aaron Lelevier
6
如果您需要在没有上下文的响应(例如重定向)中检查消息,可以使用list(r.wsgi_request._messages) - BenjaminGolder
8
看起来[0]在新版本上不起作用:*** TypeError: 'FallbackStorage' object does not support indexing。然而,它是一个可迭代对象,您可以使用任何for m in messages的语句。 - Dunatotatos
2
通过在测试用例内部使用交互式调试器(import ipdb; ipdb.set_trace())的dir()函数来查看@Jonathan。 - BenjaminGolder
1
@Jonathan https://docs.djangoproject.com/en/2.0/_modules/django/contrib/messages/middleware - OrangeDog
显示剩余3条评论

30

来自Django文档:

除了模板外,您还可以使用get_messages()

因此,您可以编写如下代码:

from django.contrib.messages import get_messages

[...]

messages = [m.message for m in get_messages(response.wsgi_request)]
self.assertIn('My message', messages)


19

这对我有效(显示所有消息):

print [m.message for m in list(response.context['messages'])]

这里还有一些实用方法,它们在我的测试类中继承自Django的TestCase。如果您更喜欢将它们作为函数使用,则需要删除self参数,并将self.fail()替换为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)))

2
仅适用于显式的ResponseContext或尝试构造ResponseContext的TemplateResponse。 - pkoch

3

更新

这篇回答最初是在Django 1.1左右写的。现在已经不再适用了。请参考@daveoncode的回答,这是一个更好的解决方案。

原始回答

我做了一个实验来测试这个问题。我在我的一个项目中更改了MESSAGE_STORAGE设置为'django.contrib.messages.storage.cookie.CookieStorage',并执行了一个我编写的检查消息的测试。测试成功了。

与您所做的关键区别在于我检索消息的方式。请看下面:

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

这个可能值得一试。

27
user.get_and_delete_messages() 在 Django 1.2 中已被弃用。 - Dave

1

我创建了一个Python类来简化消息测试:

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

在测试函数中:

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

0
简化版的僵局代码:
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)
                )

这并不完全相同,因为我的版本是检查所给文本是否“包含”在消息中(而不是“等于”)的任何/无一个。我更喜欢这样做,这样我只需要在测试用例中输入消息的关键部分,并允许消息文本的更新而不会破坏测试。 - anttikoo

0

测试助手,用于验证响应消息的数量和内容

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

可以像这样使用

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)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接