Reputation: 689
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?
Upvotes: 7
Views: 3689
Reputation: 2671
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
Upvotes: 0
Reputation: 964
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
Upvotes: 11
Reputation: 689
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)
Upvotes: 1