运行时对Django Rest Framework身份验证进行单元测试



我基本上想打开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):

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

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

有人遇到这个问题并找到解决方案吗?或者变通办法?

以下解决方法对我来说效果很好:

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

我只是想提一下我是如何解决这个问题的。它不漂亮,如果有人有任何清理建议,我们非常欢迎!正如我前面提到的,我遇到的问题记录在这里(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)

哇,太烦人了。

下面是一个处理重新加载的通用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

最新更新