【问题标题】:Why don't my Django unittests know that MessageMiddleware is installed?为什么我的 Django 单元测试不知道安装了 MessageMiddleware?
【发布时间】:2012-08-09 21:50:44
【问题描述】:

我正在开发一个 Django 项目,并正在为它编写单元测试。但是,在测试中,当我尝试登录用户时,出现此错误:

MessageFailure: You cannot add messages without installing django.contrib.messages.middleware.MessageMiddleware

在实际站点上登录可以正常工作 -- 并且使用 MessageMiddleware 显示登录消息。

在我的测试中,如果我这样做:

from django.conf import settings
print settings.MIDDLEWARE_CLASSES

然后它输出这个:

('django.middleware.cache.UpdateCacheMiddleware',
'django.middleware.common.CommonMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
'django.middleware.cache.FetchFromCacheMiddleware',
'debug_toolbar.middleware.DebugToolbarMiddleware')

这似乎表明在运行测试时安装了 MessageMiddleware。

我是否缺少明显的步骤?

更新

根据下面的建议,它看起来确实是一个设置问题。

我目前有这样的settings/__init__.py

try:
    from settings.development import *
except ImportError:
    pass

settings/defaults.py 包含大部分标准设置(包括MIDDLEWARE_CLASSES)。然后settings.development.py 会覆盖其中的一些默认值,如下所示:

from defaults import *

DEBUG = True
# etc

看起来我的开发网站本身运行良好,使用开发设置。但是,尽管测试似乎可以加载设置(默认和开发)settings.DEBUG 设置为False。我不知道为什么,或者这是否是问题的原因。

【问题讨论】:

  • 您能否解决您的问题。如果是这样,你能分享一下如何吗?我面临同样的事情,从 git repo 运行最新的 Django 1.6。
  • 我最终试图让我的测试绕过它,这意味着到目前为止,任何引发这个问题的棘手测试最终都没有被编写出来。这并不理想。祝你好运。

标签: django unit-testing messages middleware


【解决方案1】:

Django 1.4 has a expected behavior 当您使用 RequestFactory 创建请求时会触发此错误。

要解决此问题,请使用 RequestFactory 创建您的请求并执行以下操作:

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

为我工作!

【讨论】:

  • 这实际上不是错误,而是预期的行为
  • 这应该进入“测试消息”部分的文档中
  • @StephanHoyer 虽然(失败的)行为可能是意料之中的,但错误消息本身似乎具有很大的误导性:-(
  • 仍在使用 Django 1.6。这拯救了我的一天,谢谢;)
  • 在 2.0.6 中仍然存在
【解决方案2】:

当我在修补消息时遇到问题时,我发现解决方案是从被测类(过时的 Django 版本 BTW,YMMV)中修补模块。伪代码如下。

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

【讨论】:

    【解决方案3】:

    当我从单元测试中调用 login_callback 信号接收器函数时,这发生在我身上,解决问题的方法是:

    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

    【讨论】:

      【解决方案4】:

      在我的情况下(django 1.8),当单元测试为user_logged_in 信号调用信号处理程序时会出现此问题,看起来消息应用程序尚未被调用,即request._messages 尚未设置。这失败了:

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

      在普通视图函数(之后调用)中对 messages.warning 的相同调用没有任何问题。

      我基于https://code.djangoproject.com/ticket/17971 的建议之一的解决方法,仅在信号处理函数中使用fail_silently 参数,即这解决了我的问题:

      messages.warning(request, "user has logged in",
                       fail_silently=True )
      

      【讨论】:

        【解决方案5】:

        解决这个问题的一个非常优雅的方法是使用 mock 模拟消息模块

        假设您在名为 myapp 的应用中有一个名为 FooView 的基于类的视图

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

        您现在可以使用

        进行测试
        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)
        

        【讨论】:

          【解决方案6】:

          这建立在Tarsis Azevedo's answer 之上,通过在下面创建一个MessagingRequest 帮助器类。

          假设 KittenAdmin 我希望获得 100% 的测试覆盖率:

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

          我创建了一个 MessagingRequest 帮助器类,用于test_helpers.py 文件:

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

          然后在标准的 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)
          

          结果:

          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%   
          

          【讨论】:

            【解决方案7】:

            测试创建自定义(测试)数据库。也许您那里没有消息或其他什么...也许您需要 setUp() 固定装置或其他什么?

            需要更多信息才能正确回答。

            为什么不简单地做类似的事情?你确定在调试模式下运行测试对吗?

            # settings.py
            DEBUG = True
            
            from django.conf import settings
            # where message is sent:
            if not settings.DEBUG:
                # send your message ... 
            

            【讨论】:

            • 我已在问题中添加了有关我的设置的更多信息。似乎正在导入它们,但 DEBUG 在设置中为 True 时为 False。
            【解决方案8】:

            如果您在中间件中发现问题,那么您并不是在进行“单元测试”。单元测试测试一个功能单元。如果您与系统的其他部分进行交互,那么您就是在进行所谓的“集成”测试。

            你应该尝试编写更好的测试,这样的问题不应该出现。试试RequestFactory。 ;)

            def test_some_view(self):
                factory = RequestFactory()
                user = get_mock_user()
                request = factory.get("/my/view")
                request.user = user
                response = my_view(request)
                self.asssertEqual(status_code, 200)
            

            【讨论】:

            • 我不太明白这一点,或者它在这种情况下有什么帮助,但我会再读几遍这些文档,希望它会深入人心!谢谢。
            • 啊,我想它被点击了。唯一的问题 - 我正在使用基于类的视图,但我不确定如何使用它而不是 my_view(request)... 任何指针?
            • 知道了:MyDetailView.as_view()(request, slug='my-item-slug')。在 factory.get() 中也必须指定 slug 似乎很奇怪,但这似乎可行,谢谢!
            • 您可能正在使用CMS?您是否已将带有页面的数据库数据复制到您的测试数据库中?
            • 管理员操作是将请求作为参数的函数或方法,您不想因为它足够简单或者因为结果应该更复杂(调用子函数 -您可以在没有请求的情况下实际测试的那个 - 返回(u"message foo", LOG_LEVEL) 的列表并将它们处理回原始函数)。这到头来也好不了多少……
            【解决方案9】:

            你只有一个settings.py吗?

            【讨论】:

            • 我有一个设置文件导入一些默认值。更多信息添加到问题中。
            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 2011-08-18
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2021-02-25
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多