Why don't my Django unittests know that MessageMiddleware is installed?

前端 未结 9 689
傲寒
傲寒 2020-12-14 05:54

I\'m working on a Django project and am writing unittests for it. However, in a test, when I try and log a user in, I get this error:

MessageFailure: You can         


        
相关标签:
9条回答
  • 2020-12-14 06:27

    Django 1.4 has a bug when you create the request with RequestFactory.

    To resolve this issue, create your request with RequestFactory and do this:

    from django.contrib.messages.storage.fallback import FallbackStorage
    setattr(request, 'session', 'session')
    messages = FallbackStorage(request)
    setattr(request, '_messages', messages)
    

    Works for me!

    0 讨论(0)
  • 2020-12-14 06:28

    In my case (django 1.8) this problem occurs in when unit-test calls signal handler for user_logged_in signal, looks like messages app has not been called, i.e. request._messages is not yet set. This fails:

    from django.contrib.auth.signals import user_logged_in
    ...
    
    @receiver(user_logged_in)
    def user_logged_in_handler(sender, user, request, **kwargs):
    
        ...
        messages.warning(request, "user has logged in")
    

    the same call to messages.warning in normal view function (that is called after) works without any issues.

    A workaround I based on one of the suggestions from https://code.djangoproject.com/ticket/17971, use fail_silently argument only in signal handler function, i.e. this solved my problem:

    messages.warning(request, "user has logged in",
                     fail_silently=True )
    
    0 讨论(0)
  • 2020-12-14 06:34

    This happened to me in the login_callback signal receiver function when called from a unit test, and the way around the problem was:

    from django.contrib.messages.storage import default_storage
    
    @receiver(user_logged_in)
    def login_callback(sender, user, request, **kwargs):
        if not hasattr(request, '_messages'):  # fails for tests
            request._messages = default_storage(request)
    

    Django 2.0.x

    0 讨论(0)
  • 2020-12-14 06:39

    This builds on Tarsis Azevedo's answer by creating a MessagingRequest helper class below.

    Given say a KittenAdmin I'd want to get 100% test coverage for:

    from django.contrib import admin, messages
    
    class KittenAdmin(admin.ModelAdmin):
        def warm_fuzzy_method(self, request):
            messages.warning(request, 'Can I haz cheezburger?')
    

    I created a MessagingRequest helper class to use in say a test_helpers.py file:

    from django.contrib.messages.storage.fallback import FallbackStorage
    from django.http import HttpRequest
    
    class MessagingRequest(HttpRequest):
        session = 'session'
    
        def __init__(self):
            super(MessagingRequest, self).__init__()
            self._messages = FallbackStorage(self)
    
        def get_messages(self):
            return getattr(self._messages, '_queued_messages')
    
        def get_message_strings(self):
            return [str(m) for m in self.get_messages()]
    

    Then in a standard Django tests.py:

    from django.contrib.admin.sites import AdminSite
    from django.test import TestCase
    
    from cats.kitten.admin import KittenAdmin
    from cats.kitten.models import Kitten
    from cats.kitten.test_helpers import MessagingRequest
    
    class KittenAdminTest(TestCase):
        def test_kitten_admin_message(self):
            admin = KittenAdmin(model=Kitten, admin_site=AdminSite())
            expect = ['Can I haz cheezburger?']
            request = MessagingRequest()
            admin.warm_fuzzy_method(request)
            self.assertEqual(request.get_message_strings(), expect)
    

    Results:

    coverage run --include='cats/kitten/*' manage.py test; coverage report -m
    Creating test database for alias 'default'...
    .
    ----------------------------------------------------------------------
    Ran 1 test in 0.001s
    
    OK
    Destroying test database for alias 'default'...
    Name                                     Stmts   Miss  Cover   Missing
    ----------------------------------------------------------------------
    cats/kitten/__init__.py                      0      0   100%   
    cats/kitten/admin.py                         4      0   100%   
    cats/kitten/migrations/0001_initial.py       5      0   100%   
    cats/kitten/migrations/__init__.py           0      0   100%   
    cats/kitten/models.py                        3      0   100%   
    cats/kitten/test_helpers.py                 11      0   100%   
    cats/kitten/tests.py                        12      0   100%   
    ----------------------------------------------------------------------
    TOTAL                                       35      0   100%   
    
    0 讨论(0)
  • 2020-12-14 06:40

    A way to solve this quite elegant is to mock the messages module using mock

    Say you have a class based view named FooView in app named myapp

    from django.contrib import messages
    from django.views.generic import TemplateView
    
    class FooView(TemplateView):
        def post(self, request, *args, **kwargs):
            ...
            messages.add_message(request, messages.SUCCESS, '\o/ Profit \o/')
            ...
    

    You now can test it with

    def test_successful_post(self):
        mock_messages = patch('myapp.views.FooView.messages').start()
        mock_messages.SUCCESS = success = 'super duper'
        request = self.rf.post('/', {})
        view = FooView.as_view()
        response = view(request)
        msg = _(u'\o/ Profit \o/')
        mock_messages.add_message.assert_called_with(request, success, msg)
    
    0 讨论(0)
  • 2020-12-14 06:44

    I found when I had a problem patching messages the solution was to patch the module from within the class under test (obsolete Django version BTW, YMMV). Pseudocode follows.

    my_module.py:

    from django.contrib import messages
    
    
    class MyClass:
    
        def help(self):
            messages.add_message(self.request, messages.ERROR, "Foobar!")
    

    test_my_module.py:

    from unittest import patch, MagicMock
    from my_module import MyClass
    
    
    class TestMyClass(TestCase):
    
        def test_help(self):
            with patch("my_module.messages") as mock_messages:
                mock_messages.add_message = MagicMock()
                MyClass().help()  # shouldn't complain about middleware
    
    0 讨论(0)
提交回复
热议问题