Wie kann ich Django-Nachrichten testen?

78

In meiner Django-Anwendung versuche ich, einen Komponententest zu schreiben, der eine Aktion ausführt und dann die Nachrichten in der Antwort überprüft.

Soweit ich das beurteilen kann, gibt es dafür keinen guten Weg.

Ich verwende die CookieStorage-Speichermethode und möchte etwas Ähnliches wie das Folgende tun:

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

Das Problem ist, alles was ich zurück bekomme ist a

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

Wie kann ich daraus etwas Nützliches machen oder mache ich alles falsch?

Danke, Daniel

dvydra
quelle
das funktioniert, aber ... im ernst? response.context ['messages'] ._ get () [0] [0] .__ dict __ ['message']
dvydra
5
Sie können versuchen, diesen nicht so schönen Code in einer schönen Funktion zu kapseln assert_has_message(response, msg_text)und ihn überall dort zu verwenden, wo Sie möchten. Wenn Sie einen besseren Weg finden, um auf die Nachrichten zuzugreifen, ändern Sie die Funktion einfach an einer Stelle.
nkrkv
@nailxx, ja, das ist im Grunde, was ich getan habe, aber es macht mich unwohl :)
dvydra
3
Dies funktioniert auch: messages_list = CookieStorage(response)._decode(response.cookies['messages'].value) Hiermit erhalten Sie eine Liste der Objekte django.contrib.messages.storage.base.Message.
dvydra
@dvydra Wenn Sie noch in der Nähe sind, möchten Sie möglicherweise die akzeptierte Antwort ändern
OrangeDog

Antworten:

89

Ich fand einen wirklich einfachen Ansatz:

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

Wenn Sie in einer Antwort ohne Kontext nach Nachrichten suchen müssen, können Sie Folgendes verwenden:

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

Der Fallback-Speicher unterstützt keine Indizierung, ist jedoch iterierbar.

Daveoncode
quelle
2
Ich fand auch, dass self.assertEqual(m[0].message, 'my message')funktioniert
Aaron Lelevier
6
Wenn Sie nach Nachrichten für eine Antwort suchen müssen, die keinen Kontext hat (z. B. eine Umleitung), können Sielist(r.wsgi_request._messages)
BenjaminGolder
6
Es sieht so aus, als ob [0] bei den neuen Versionen nicht funktioniert : *** TypeError: 'FallbackStorage' object does not support indexing. Es ist jedoch ein Iterable und Sie können jedes for m in messagesKonstrukt verwenden.
Dunatotatos
2
@ Jonathan durch Verwendung dir()in einem interaktiven Debugger ( import ipdb; ipdb.set_trace()) in einem Testfall.
Benjamin
17

Dies funktioniert bei mir (zeigt alle Nachrichten an):

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

Hier sind auch einige Dienstprogrammmethoden, die ich in einer Testklasse habe, die von Djangos TestCase geerbt wurde. Wenn Sie sie lieber als Funktionen haben möchten, entfernen Sie die selfArgumente und ersetzen Sie sie self.fail()durch 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
quelle
2
Funktioniert nur mit explizitem ResponseContext oder TemplateResponse (der versucht, einen ResponseContext zu erstellen).
pkoch
17

Von Django-Dokumentation :

Außerhalb von Vorlagen können Sie get_messages () verwenden.

Sie könnten also so etwas schreiben wie:

from django.contrib.messages import get_messages

[...]

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

Moppag
quelle
3

Aktualisieren

Meine ursprüngliche Antwort wurde geschrieben, als Django noch ungefähr 1.1 war. Diese Antwort ist nicht mehr relevant. Siehe die Antwort von @ daveoncodeEine bessere Lösung finden .

Ursprüngliche Antwort

Ich habe ein Experiment durchgeführt, um dies zu testen. Ich habe die MESSAGE_STORAGEEinstellung in einem meiner Projekte in geändert'django.contrib.messages.storage.cookie.CookieStorage' und einen Test ausgeführt, den ich geschrieben habe, um nach Nachrichten zu suchen. Es funktionierte.

Der Hauptunterschied zu dem, was Sie getan haben, ist die Art und Weise, wie ich Nachrichten abgerufen habe. Siehe unten:

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

Dies kann einen Versuch wert sein.

Manoj Govindan
quelle
27
user.get_and_delete_messages () war in Django 1.2
Dave
0

Einfachere Version der Pattsituation:

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
quelle
Dies ist nicht genau das Gleiche, da meine Version prüft, ob der angegebene Text in einer / keiner der Nachrichten enthalten ist (nicht gleich ). Ich bevorzuge es so, so dass ich nur den Schlüsselteil der Nachricht in einen Testfall eingeben und zulassen muss, dass der Nachrichtentext aktualisiert wird, ohne den Test zu unterbrechen.
Anttikoo
0

Testen Sie die Helfer zur Überprüfung der Anzahl und des Inhalts der Antwortnachrichten

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

Kann so verwendet werden

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
quelle