#24721: Introduce something similar to mail.outbox for messages
--------------------------------------------+------------------------
Reporter: mjtamlyn | Owner: nobody
Type: New feature | Status: new
Component: contrib.messages | Version: master
Severity: Normal | Keywords:
Triage Stage: Unreviewed | Has patch: 0
Needs documentation: 0 | Needs tests: 0
Patch needs improvement: 0 | Easy pickings: 0
UI/UX: 0 |
--------------------------------------------+------------------------
'''TL;DR'''
- Testing messages is tricky, and depends on the choice of storage used
- Testing email has been abstracted nicely, let's copy that
- Doing so from a contrib app is hard, let's leverage app configs as a
place for hooks for contrib apps to inject their own testing needs into
Django's normal test environment
'''The long version'''
When testing emails with Django, it automatically changes your email
backend to a clever in memory one, and exposes {{{mail.outbox}}} which
will contain any emails sent during that test and tidy itself up
afterwards for you.
With messages we have no such machinery. If you want to check a message
has been sent on a GET this is quite straightforwards as you can simply
check {{{response.context['messages']}}} for it. However with POST
requests it is rather more complex, especially if you use
{{{assertRedirects}}} in it's normal form - the check of the destination
of the redirect will remove the message!
As an example, consider this test, which is based on an existing test
({{{messages.tests.test_mixins.SuccessMessageMixinTests}}}):
{{{
from django.core.urlresolvers import reverse
from django.test import TestCase, override_settings
@override_settings(ROOT_URLCONF='messages_tests.urls')
class TestRedirectionMessage(TestCase):
def test_simple(self):
author = {'name': 'John Doe',
'slug': 'success-msg'}
add_url = reverse('add_success_msg')
response = self.client.post(add_url, author)
# response.context['messages'] does not exist as response is a 302
self.assertRedirects(response, reverse('show_message'))
# Message is now gone, even if I were to make another GET request.
}}}
In this case, the original test inspects {{{response.cookies}}} for the
302 response, which is fairly easy, but it's more complex with other
message storages. If you use the default {{{FallbackStorage}}}, you would
have to change your test if the message got too long to inspect the
session storage instead of the cookie...
Here's the version of that test I would like:
{{{
def test_simple(self):
author = {'name': 'John Doe',
'slug': 'success-msg'}
add_url = reverse('add_success_msg')
response = self.client.post(add_url, author)
self.assertRedirects(response, reverse('show_message'))
self.assertEqual(len(messages.outbox), 1)
self.assertEqual(messages.outbox[0].message, 'John Doe was created
successfully')
self.assertEqual(messages.outbox[0].level, messages.SUCCESS)
}}}
The use of the name {{{outbox}}} here is somewhat strange I admit, but as
yet I haven't found an alternative.
The actual implementation detail is fairly straightforwards in some sense
- create a new {{{InMemoryStorage}}} with the same basic infrastructure as
the corresponding email backend. The difficult part is that due to the
fact this is a contrib app. The engineering to make the email backend work
takes place in two distinct locations - first in the
{{{setup_test_environment}}} to initialise it, and then in
{{{TestCase._pre_setup}}} to reset before each test.
The best proposal I have for designing this is to add some optional
methods to the {{{AppConfig}}} class which are hooks called by both of
these methods. This would also be extremely useful for other packages
which interact with external resources and/or have alternate backends. For
example, django-redis could use these hooks to switch to another redis
database and automatically clear it before each test (like we do with
other data stores), or django-celery could automatically switch to using
an in-memory broker and provide a good api for checking that the task has
been queued, then flushing the queue later if desired, if not then reset
the queue after each test. This is likely to be more expressive and
performant than {{{CELERY_ALWAYS_EAGER = True}}}.
Backwards compatibility with existing tests which expect the request to
have been modified with messages is an issue, I suspect this may need to
be phased in, or set as non default. This could be done via an alternative
app config if this is the route we choose to go.
--
Ticket URL: <https://code.djangoproject.com/ticket/24721>
Django <https://code.djangoproject.com/>
The Web framework for perfectionists with deadlines.
--
You received this message because you are subscribed to the Google Groups
"Django updates" group.
To unsubscribe from this group and stop receiving emails from it, send an email
to [email protected].
To post to this group, send email to [email protected].
To view this discussion on the web visit
https://groups.google.com/d/msgid/django-updates/051.90aa664cf7c24b6588fc14e4c7308f0e%40djangoproject.com.
For more options, visit https://groups.google.com/d/optout.