Django Testing - check messages for a view that redirects
Asked Answered
D

5

30

I have been writing tests for one of my django applications and have been looking to get around this problem for quite some time now. I have a view that sends messages using django.contrib.messages for different cases. The view looks something like the following.

from django.contrib import messages
from django.shortcuts import redirect

import custom_messages

def some_view(request):
    """ This is a sample view for testing purposes.
    """

    some_condition = models.SomeModel.objects.get_or_none(
        condition=some_condition)
    if some_condition:
        messages.success(request, custom_message.SUCCESS)
    else:
        messages.error(request, custom_message.ERROR)
    redirect(some_other_view)

Now, while testing this view client.get's response does not contain the context dictionary that contains the messages as this view uses a redirect. For views that render templates we can get access to the messages list using messages = response.context.get('messages'). How can we get access messages for a view that redirects?

Drivein answered 22/4, 2013 at 9:3 Comment(2)
Not sure if this will fit your need but you can pass get variables to identify what has happened: redirect(reverse(some_other_view) + '?user_added=true')Pacer
I am actually already testing the condition being used in the view in my test. Here I am talking about explicitly testing the message that was sent.Drivein
T
52

Use the follow=True option in the client.get() call, and the client will follow the redirect. You can then test that the message is in the context of the view you redirected to.

def test_some_view(self):
    # use follow=True to follow redirect
    response = self.client.get('/some-url/', follow=True)

    # don't really need to check status code because assertRedirects will check it
    self.assertEqual(response.status_code, 200)
    self.assertRedirects(response, '/some-other-url/')

    # get message from context and check that expected text is there
    message = list(response.context.get('messages'))[0]
    self.assertEqual(message.tags, "success")
    self.assertTrue("success text" in message.message)
Tantalus answered 22/4, 2013 at 9:38 Comment(7)
Thanks, that worked. Though, using follow=True changes the expected redirect code from 302 to 200 as it follows the redirected view.Drivein
Yes, following the redirect means that the response has status code 200. There is an assertRedirects method that you can use to test the redirect.Tantalus
yup, that's what i am using now :)Drivein
This also works for client.post() to add follow=TrueBuchanan
@bbrik the answer has now been updated, sorry I didn't see your comment sooner.Tantalus
When using a RequestFactory to create a request in order to store session data, this doesn't work. The response I get after passing the request directly to the view doesn't have a context, request, or wsgi_request attr.Hanni
@SeanTilson the original question and this answer are both using the test client instead of request factory. I can’t help with how to get it to work with request factory.Tantalus
S
9

You can use get_messages() with response.wsgi_request like this (tested in Django 1.10):

from django.contrib.messages import get_messages  
...
def test_view(self):
    response = self.client.get('/some-url/') # you don't need follow=True
    self.assertRedirects(response, '/some-other-url/')
    # each element is an instance of  django.contrib.messages.storage.base.Message
    all_messages = [msg for msg in get_messages(response.wsgi_request)]

    # here's how you test the first message
    self.assertEqual(all_messages[0].tags, "success")
    self.assertEqual(all_messages[0].message, "you have done well")
Sellers answered 15/2, 2017 at 14:34 Comment(2)
This worked for me, whereas the Alasdair's method didn't show any messages. I'm on Django 1.11.Chinchin
What about when the response doesn't have a wsgi_request attr?Hanni
A
1

If your views are redirecting and you use follow=true in your request to the test client the above doesn't work. I ended up writing a helper function to get the first (and in my case, only) message sent with the response.

@classmethod
def getmessage(cls, response):
    """Helper method to return message from response """
    for c in response.context:
        message = [m for m in c.get('messages')][0]
        if message:
            return message

You include this within your test class and use it like this:

message = self.getmessage(response)

Where response is what you get back from a get or post to a Client.

This is a little fragile but hopefully it saves someone else some time.

Ahoufe answered 12/9, 2013 at 0:59 Comment(1)
You don't need that list comprehension, just use list(messages) or tuple(messages).Staphylorrhaphy
L
1

I had the same problem when using a 3rd party app.

If you want to get the messages from a view that returns an HttpResponseRedict (from which you can't access the context) from within another view, you can use get_messages(request)

from django.contrib.messages import get_messages  

storage = get_messages(request)  
for message in storage:  
    do_something_with_the_message(message)  

This clears the message storage though, so if you want to access the messages from a template later on, add:

storage.used = False
Linen answered 24/6, 2014 at 7:41 Comment(0)
E
1

Alternative method mocking messages (doesn't need to follow redirect):

from mock import ANY, patch
from django.contrib import messages

@patch('myapp.views.messages.add_message')
def test_some_view(self, mock_add_message):
    r = self.client.get('/some-url/')
    mock_add_message.assert_called_once_with(ANY, messages.ERROR, 'Expected message.')  # or assert_called_with, assert_has_calls...
Emanuelemanuela answered 23/5, 2017 at 7:26 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.