Reputation: 3927
I am using DRF with the JWT package for authentication. Now, I'm trying to write a unit test that authenticates itself with a JWT token. No matter how I try it, I can't get the test API client to authenticate itself via JWT. If I do the same with an API client (in my case, Postman), everything works.
This is the test case:
from django.urls import reverse
from rest_framework.test import APITestCase
from rest_framework_jwt.settings import api_settings
from backend.factories import member_factory
jwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLER
jwt_encode_handler = api_settings.JWT_ENCODE_HANDLER
class MemberTests(APITestCase):
def test_get_member(self):
member = member_factory()
payload = jwt_payload_handler(member.user)
token = jwt_encode_handler(payload)
self.client.credentials(Authorization='JWT {0}'.format(token))
response = self.client.get(reverse('member-detail', kwargs={'pk': member.pk}))
assert response.status_code == 200
But I always get a 401 Authentication credentials were not provided
.
In response.request
I see the token is there, it's just not being applied I guess.
If I rewrite the test to use rest_framework.test.RequestsClient
and actually send it to the live_server URL, it works.
Any help on this?
P.S.: I am aware of force_authenticate()
and login, but I would like my unit tests to access the API the same as the API client will in production.
Upvotes: 37
Views: 25722
Reputation: 11
from rest_framework.test import APITestCase
from django.contrib.auth import get_user_model
from django.urls import reverse
from rest_framework import status
from rest_framework_simplejwt.tokens import RefreshToken
User = get_user_model()
class TestCaseBase(APITestCase):
@property
def bearer_token(self):
# assuming there is a user in User model
user = User.objects.create_user(
email='[email protected]', password='12345678'
)
refresh = RefreshToken.for_user(user)
return {"HTTP_AUTHORIZATION": f'Bearer {refresh.access_token}'}
class CategoriesTestClass(TestCaseBase):
url = reverse('categories-list')
def test_get_list_no_auth(self):
response = self.client.get(self.url)
self.assertEqual(
response.status_code, status.HTTP_401_UNAUTHORIZED, response.data
)
def test_get_list(self):
response = self.client.get(self.url, **self.bearer_token)
self.assertEqual(response.status_code, status.HTTP_200_OK)`enter code here`
Upvotes: 1
Reputation: 257
I'm using DRF and simple-jwt and I had to use Bearer instead of JWT in the http auth header: HTTP_AUTHORIZATION=f'Bearer {token}'
Full code:
def setUp(self):
username = "[email protected]"
password = "strongP@assword!"
self.user = User.objects.create_user(username, username, password)
jwt_fetch_data = {
'username':username,
'password':password
}
url = reverse('token_obtain_pair')
response = self.client.post(url, jwt_fetch_data, format='json')
token = response.data['access']
self.client.credentials(HTTP_AUTHORIZATION=f'Bearer {token}')
def test_post(self):
response = self.client.get('/some-url/',
data={'format': 'json'}
)
self.assertEqual(response.status_code, status.HTTP_200_OK)
Upvotes: 1
Reputation: 4847
Well, since i was using django unit test client, i just created a simple base test class with a bearer token property:
import json
from django.test import TestCase
from django.contrib.auth import User
from rest_framework.test import APIClient
from rest_framework_simplejwt.tokens import RefreshToken
class TestCaseBase(TestCase):
@property
def bearer_token(self):
# assuming there is a user in User model
user = User.objects.get(id=1)
refresh = RefreshToken.for_user(user)
return {"HTTP_AUTHORIZATION":f'Bearer {refresh.access_token}'}
In my django unit tests:
class SomeTestClass(TestCaseBase):
url = "someurl"
def test_get_something(self):
self.client.get(self.url, **self.bearer_token)
def test_post_something(self):
self.client.post(self.url, data={"key":"value"}, **self.bearer_token)
Upvotes: 2
Reputation: 69953
The following answer applies if you are using Simple JWT and pytest, and Python 3.6+. You need to create a fixture, I have called it api_client
, and you need to get the token for an existing user.
from django.contrib.auth.models import User
from rest_framework.test import APIClient
from rest_framework_simplejwt.tokens import RefreshToken
import pytest
@pytest.fixture
def api_client():
user = User.objects.create_user(username='john', email='[email protected]', password='js.sj')
client = APIClient()
refresh = RefreshToken.for_user(user)
client.credentials(HTTP_AUTHORIZATION=f'Bearer {refresh.access_token}')
return client
Notice that in the fixture above, the user is created there, but you can use another fixture to create the user and pass it to this one. The key element is the following line:
refresh = RefreshToken.for_user(user)
This line allows you to create tokens manually as explained in the docs. Once you have that token, you can use the method credentials
in order to set headers that will then be included on all subsequent requests by the test client. Notice that refresh.access_token
contains the access token.
This fixture has to be used in your tests that you require the user to be authenticated as in the following example:
@pytest.mark.django_db
def test_name_of_your_test(api_client):
# Add your logic here
url = reverse('your-url')
response = api_client.get(url)
data = response.data
assert response.status_code == HTTP_200_OK
# your asserts
Upvotes: 38
Reputation: 20140
Postman interacts with your actual database. Django uses separate database for it's test case running. Therefore a new user record needs to be created again inside your test definition before authentication testing. Hope this helps.
Upvotes: 1
Reputation: 131
Inspired by @dkarchmer, this is my code working.
I am using a custom user model which the email is used for authentication.
Pay attention to using email
field for authentication requests.
If I use username
, the response is 400_BAD_REQUEST
.
The 401_UNAUTHORIZED
usually means the credentials are not correct or the user is not activated.
def test_unusual(self):
User = get_user_model()
email = '[email protected]'
password = 'userpass1'
username = 'user'
user = User.objects.create_user(
username=username, email=email, password=password)
user.is_active = False
user.save()
obtain_url = reverse('token_obtain_pair')
resp = self.client.post(
obtain_url, {'email': email, 'password': password}, format='json')
self.assertEqual(resp.status_code, status.HTTP_401_UNAUTHORIZED)
user.is_active = True
user.save()
resp = self.client.post(
obtain_url, {'email': email, 'password': password}, format='json')
self.assertEqual(resp.status_code, status.HTTP_200_OK)
Upvotes: 1
Reputation: 71
I had similar issue, enclosed I send you my solution just to have more code to compare (tests.py).
from django.urls import reverse
from rest_framework import status
from rest_framework.test import APITestCase
from django.contrib.auth.models import User
class AuthViewsTests(APITestCase):
def setUp(self):
self.username = 'usuario'
self.password = 'contrasegna'
self.data = {
'username': self.username,
'password': self.password
}
def test_current_user(self):
# URL using path name
url = reverse('tokenAuth')
# Create a user is a workaround in order to authentication works
user = User.objects.create_user(username='usuario', email='[email protected]', password='contrasegna')
self.assertEqual(user.is_active, 1, 'Active User')
# First post to get token
response = self.client.post(url, self.data, format='json')
self.assertEqual(response.status_code, status.HTTP_200_OK, response.content)
token = response.data['token']
# Next post/get's will require the token to connect
self.client.credentials(HTTP_AUTHORIZATION='JWT {0}'.format(token))
response = self.client.get(reverse('currentUser'), data={'format': 'json'})
self.assertEqual(response.status_code, status.HTTP_200_OK, response.content)
Upvotes: 7
Reputation: 5604
Try setting up a new APIClient for this test. This is how my own test looks like
def test_api_jwt(self):
url = reverse('api-jwt-auth')
u = user_model.objects.create_user(username='user', email='[email protected]', password='pass')
u.is_active = False
u.save()
resp = self.client.post(url, {'email':'[email protected]', 'password':'pass'}, format='json')
self.assertEqual(resp.status_code, status.HTTP_400_BAD_REQUEST)
u.is_active = True
u.save()
resp = self.client.post(url, {'username':'[email protected]', 'password':'pass'}, format='json')
self.assertEqual(resp.status_code, status.HTTP_200_OK)
self.assertTrue('token' in resp.data)
token = resp.data['token']
#print(token)
verification_url = reverse('api-jwt-verify')
resp = self.client.post(verification_url, {'token': token}, format='json')
self.assertEqual(resp.status_code, status.HTTP_200_OK)
resp = self.client.post(verification_url, {'token': 'abc'}, format='json')
self.assertEqual(resp.status_code, status.HTTP_400_BAD_REQUEST)
client = APIClient()
client.credentials(HTTP_AUTHORIZATION='JWT ' + 'abc')
resp = client.get('/api/v1/account/', data={'format': 'json'})
self.assertEqual(resp.status_code, status.HTTP_401_UNAUTHORIZED)
client.credentials(HTTP_AUTHORIZATION='JWT ' + token)
resp = client.get('/api/v1/account/', data={'format': 'json'})
self.assertEqual(resp.status_code, status.HTTP_200_OK)
Upvotes: 51