6

I basically want to turn TokenAuthentication on but only for 2 unit tests. The only option I've seen so far is to use @override_settings(...) to replace the REST_FRAMEWORK settings value.

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

This isn't working. I can print the settings before and after the decorator and see that the values changed but django doesn't seem to be respecting them. It allows all requests sent using the test Client or the DRF APIClient object through without authentication. I'm getting 200 responses when I would expect 401 unauthorized.

If I insert that same dictionary into my test_settings.py file in the config folder everything works as expected. However like I said I only want to turn on authentication for a couple of unit tests, not all of them. My thought is that Django never revisits the settings for DRF after initialization. So even though the setting values are correct they are not used.

Has anyone run into this problem and found a solution? Or workaround?

Rob Carpenter
  • 602
  • 7
  • 16
  • Use the provided `from rest_framework.settings import api_settings` reviewing the source code will give you a hint how to override: https://github.com/tomchristie/django-rest-framework/blob/master/rest_framework/settings.py – petkostas Mar 31 '15 at 11:47
  • I just found this open issue, https://github.com/tomchristie/django-rest-framework/issues/2466 looks like my problem may not be solved yet :/ – Rob Carpenter Mar 31 '15 at 18:32

3 Answers3

9

The following workaround works well for me:

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
0x2207
  • 878
  • 1
  • 6
  • 20
1

Just thought I'd mention how I solved this. It's not pretty and if anyone has any suggestions to clean it up they are more than welcome! As I mentioned earlier the problem I'm having is documented here (https://github.com/tomchristie/django-rest-framework/issues/2466), but the fix is not so clear. In addition to reloading the DRF views module I also had to reload the apps views module to get it working.

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)
Rob Carpenter
  • 602
  • 7
  • 16
0

Wow that's annoying.

Here's a generic contextmanager that handles the reloading. Note that you can't import the subobject api_settings directly because DRF doesn't alter it on reload, but rather reassigns the module-level object to a new instance, so we just access it from the module directly when we need it.

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

NOTE: If you're changing other aspects of the settings, you may also have to patch the following APIView attributes:

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
DylanYoung
  • 2,423
  • 27
  • 30