【问题标题】:Unit Testing Django Rest Framework Authentication at Runtime运行时单元测试 Django Rest Framework 身份验证
【发布时间】:2015-06-04 16:45:27
【问题描述】:

我基本上想打开 TokenAuthentication 但仅用于 2 个单元测试。到目前为止,我看到的唯一选择是使用 @override_settings(...) 替换 REST_FRAMEWORK 设置值。

REST_FRAMEWORK_OVERRIDE={
    'PAGINATE_BY': 20,
    'TEST_REQUEST_DEFAULT_FORMAT': 'json',
    'DEFAULT_RENDERER_CLASSES': (
        'rest_framework.renderers.JSONRenderer',
        'rest_framework_csv.renderers.CSVRenderer',
    ),
    'DEFAULT_AUTHENTICATION_CLASSES': (
        'rest_framework.authentication.TokenAuthentication',
    ),
    'DEFAULT_PERMISSION_CLASSES': (
        'rest_framework.permissions.IsAuthenticated',
    ),
}

@override_settings(REST_FRAMEWORK=REST_FRAMEWORK_OVERRIDE)
def test_something(self):

这行不通。我可以在装饰器之前和之后打印设置,并看到值发生了变化,但 django 似乎并不尊重它们。它允许使用测试客户端或 DRF APIClient 对象发送的所有请求无需身份验证即可通过。当我预计 401 未授权时,我收到了 200 条回复。

如果我将同一个字典插入到配置文件夹中的 test_settings.py 文件中,一切都会按预期工作。但是就像我说的,我只想为几个单元测试打开身份验证,而不是全部。我的想法是 Django 在初始化后从不重新访问 DRF 的设置。因此,即使设置值正确,它们也不会被使用。

有没有人遇到过这个问题并找到了解决方案?或解决方法?

【问题讨论】:

标签: django unit-testing django-rest-framework django-settings http-token-authentication


【解决方案1】:

以下解决方法对我很有效:

from rest_framework.views import APIView
from rest_framework.permissions import IsAuthenticatedOrReadOnly
from rest_framework.authentication import TokenAuthentication

try:
    from unittest.mock import patch
except ImportError:
    from mock import patch

@patch.object(APIView, 'authentication_classes', new = [TokenAuthentication])
@patch.object(APIView, 'permission_classes', new = [IsAuthenticatedOrReadOnly])
class AuthClientTest(LiveServerTestCase):
    # your tests goes here

【讨论】:

    【解决方案2】:

    只是想我会提到我是如何解决这个问题的。它不漂亮,如果有人有任何清理它的建议,他们非常欢迎!正如我之前提到的,我遇到的问题记录在此处 (https://github.com/tomchristie/django-rest-framework/issues/2466),但修复方法还不是很清楚。除了重新加载 DRF 视图模块外,我还必须重新加载应用程序视图模块才能使其正常工作。

    import os
    import json
    from django.conf import settings
    from django.test.utils import override_settings
    from django.utils.six.moves import reload_module
    
    from rest_framework import views as drf_views
    from rest_framework.test import force_authenticate, APIRequestFactory, APIClient
    
    from apps.contact import views as cm_views
    from django.core.urlresolvers import reverse
    from django.test import TestCase
    from unittest import mock
    
    REST_FRAMEWORK_OVERRIDE={
    'PAGINATE_BY': 20,
    'TEST_REQUEST_DEFAULT_FORMAT': 'json',
    'DEFAULT_RENDERER_CLASSES': (
        'rest_framework.renderers.JSONRenderer',
        'rest_framework_csv.renderers.CSVRenderer',
    ),
    'DEFAULT_AUTHENTICATION_CLASSES': (
        'rest_framework.authentication.TokenAuthentication',
    ),
    'DEFAULT_PERMISSION_CLASSES': (
        'rest_framework.permissions.IsAuthenticated',
    ),
    }
    
    def test_authenticated(self):
        with override_settings(REST_FRAMEWORK=REST_FRAMEWORK_OVERRIDE):
            # The two lines below will make sure the views have the correct authentication_classes and permission_classes
            reload_module(drf_views)
            reload_module(cm_views)
            from apps.contact.views import AccountView
            UserModelGet = mock.Mock(return_value=self.account)
            factory = APIRequestFactory()
            user = UserModelGet(user='username')
            view = AccountView.as_view()
    
            # Test non existent account
            path = self.get_account_path("1thiswillneverexist")
            request = factory.get(path)
            force_authenticate(request, user=user)
            response = view(request, account_name=os.path.basename(request.path))
            self.assertEquals(response.status_code, 200, "Wrong status code")
            self.assertEqual(json.loads(str(response.content, encoding='utf-8')), [], "Content not correct for authenticated account request")
        # Reset the views permission_classes and authentication_classes to what they were before this test
        reload_module(cm_views)
        reload_module(drf_views)
    

    【讨论】:

    • 如果您想避免重新加载模块,请参阅下面的答案,这可能会出现问题。
    【解决方案3】:

    哇,真烦人。

    这是处理重新加载的通用contextmanager。请注意,您不能直接导入子对象api_settings,因为 DRF 不会在重新加载时更改它,而是将模块级对象重新分配给新实例,因此我们只需在需要时直接从模块中访问它。

    from rest_framework import settings as api_conf
    
    @contextmanager
    def override_rest_framework_settings(new_settings):
        with override_settings(REST_FRAMEWORK=new_settings):
            # NOTE: `reload_api_settings` is a signal handler, so we have to pass a 
            # couple things in to get it working.
            api_conf.reload_api_settings(setting="REST_FRAMEWORK", value="")
            with mock.patch.multiple(
                "rest_framework.views.APIView",
                authentication_classes=api_conf.api_settings.DEFAULT_AUTHENTICATION_CLASSES,
            ):
                yield
        api_conf.reload_api_settings(setting="REST_FRAMEWORK", value="")
    

    注意:如果您要更改设置的其他方面,您可能还需要修补以下APIView 属性:

    renderer_classes = api_settings.DEFAULT_RENDERER_CLASSES
    parser_classes = api_settings.DEFAULT_PARSER_CLASSES
    authentication_classes = api_settings.DEFAULT_AUTHENTICATION_CLASSES
    throttle_classes = api_settings.DEFAULT_THROTTLE_CLASSES
    permission_classes = api_settings.DEFAULT_PERMISSION_CLASSES
    content_negotiation_class = api_settings.DEFAULT_CONTENT_NEGOTIATION_CLASS
    metadata_class = api_settings.DEFAULT_METADATA_CLASS
    versioning_class = api_settings.DEFAULT_VERSIONING_CLASS
    settings = api_settings
    

    【讨论】:

      猜你喜欢
      • 2015-06-01
      • 2018-07-04
      • 2015-09-25
      • 2013-12-29
      • 2017-08-15
      • 2019-01-01
      • 1970-01-01
      • 2013-05-03
      • 2021-09-09
      相关资源
      最近更新 更多