7

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?

2

3 Answers 3

11

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 
Sign up to request clarification or add additional context in comments.

Comments

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) 

1 Comment

See my answer below if you'd like to avoid reloading modules, which can be problematic.
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 

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.