简体   繁体   English

Django Rest 框架 JWT 单元测试

[英]Django Rest Framework JWT Unit Test

I am using DRF with the JWT package for authentication.我将 DRF 与 JWT package 一起用于身份验证。 Now, I'm trying to write a unit test that authenticates itself with a JWT token.现在,我正在尝试编写一个单元测试,使用 JWT 令牌对自身进行身份验证。 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.无论我如何尝试,我都无法让测试 API 客户端通过 JWT 对自身进行身份验证。如果我对 API 客户端(在我的例子中是 Postman)执行相同的操作,一切正常。

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 .但我总是得到401 Authentication credentials were not provided

In response.request I see the token is there, it's just not being applied I guess.response.request中,我看到令牌在那里,我猜它只是没有被应用。

If I rewrite the test to use rest_framework.test.RequestsClient and actually send it to the live_server URL, it works.如果我重写测试以使用rest_framework.test.RequestsClient并将其实际发送到live_server URL,它会起作用。

Any help on this?有什么帮助吗?

PS: 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. PS:我知道force_authenticate()login ,但我希望我的单元测试能够访问 API ,就像生产中的 API 客户端一样。

Try setting up a new APIClient for this test.尝试为此测试设置一个新的 APIClient。 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='user@foo.com', password='pass')
    u.is_active = False
    u.save()

    resp = self.client.post(url, {'email':'user@foo.com', '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':'user@foo.com', '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)

The following answer applies if you are using Simple JWT and pytest , and Python 3.6+.如果您使用的是Simple JWTpytest以及 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.您需要创建一个固定装置,我将其称为api_client ,并且您需要为现有用户获取令牌。

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='js@js.com', 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.获得该令牌后,您可以使用方法credentials来设置标头,然后这些标头将包含在测试客户端的所有后续请求中。 Notice that refresh.access_token contains the access token.请注意, refresh.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

I had similar issue, enclosed I send you my solution just to have more code to compare (tests.py).我有类似的问题,附上我给你我的解决方案只是为了有更多的代码来比较(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='usuario@mail.com', 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)

Inspired by @dkarchmer, this is my code working.受@dkarchmer 的启发,这是我的代码。
I am using a custom user model which the email is used for authentication.我正在使用电子邮件用于身份验证的自定义用户模型。
Pay attention to using email field for authentication requests.注意使用email字段进行身份验证请求。 If I use username , the response is 400_BAD_REQUEST .如果我使用username ,则响应为400_BAD_REQUEST The 401_UNAUTHORIZED usually means the credentials are not correct or the user is not activated. 401_UNAUTHORIZED通常意味着凭据不正确或用户未激活。

def test_unusual(self):
        User = get_user_model()
        email = 'user@test.com'
        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)

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}'我正在使用 DRF 和 simple-jwt,我不得不在 http auth header 中使用 Bearer 而不是 JWT: HTTP_AUTHORIZATION=f'Bearer {token}'

Full code:完整代码:

def setUp(self):
    username = "tim@me.com"
    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)

Postman interacts with your actual database. Postman 与您的实际数据库交互。 Django uses separate database for it's test case running. Django 使用单独的数据库来运行它的测试用例。 Therefore a new user record needs to be created again inside your test definition before authentication testing.因此,在身份验证测试之前,需要在测试定义中再次创建新的用户记录。 Hope this helps.希望这可以帮助。

Well, since i was using django unit test client, i just created a simple base test class with a bearer token property:好吧,因为我使用的是 django 单元测试客户端,所以我刚刚创建了一个带有不记名令牌属性的简单基础测试类:

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)
        client = APIClient()
        refresh = RefreshToken.for_user(user)
        return {"HTTP_AUTHORIZATION":f'Bearer {refresh.access_token}'}

In my django unit tests:在我的 Django 单元测试中:

class SomeTestClass(TestCaseBase):
    url = "someurl"

    def test_get_something(self):
        self.client.get(self.url, **bearer_token)

    def test_post_something(self):
        self.client.post(self.url, data={"key":"value"}, **self.bearer_token)

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='test@user.me', 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`

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM