【问题标题】:Django Rest Framework JWT Unit TestDjango Rest Framework JWT 单元测试
【发布时间】:2018-05-14 13:40:46
【问题描述】:

我使用 DRF 和 JWT 包进行身份验证。现在,我正在尝试编写一个使用 JWT 令牌对自身进行身份验证的单元测试。无论我如何尝试,我都无法让测试 API 客户端通过 JWT 进行身份验证。如果我对 API 客户端(在我的例子中是 Postman)做同样的事情,那么一切正常。

这是测试用例:

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

但我总是收到401 Authentication credentials were not provided

response.request 我看到令牌在那里,我猜它只是没有被应用。

如果我重写测试以使用rest_framework.test.RequestsClient 并将其实际发送到live_server URL,它就可以工作。

有什么帮助吗?

P.S.:我知道 force_authenticate()login,但我希望我的单元测试能够像 API 客户端在生产中一样访问 API。

【问题讨论】:

  • 你们两个成就了我的一天。谢谢

标签: python django django-rest-framework jwt


【解决方案1】:

尝试为此测试设置一个新的 APIClient。这就是我自己的测试的样子

 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)

【讨论】:

  • 谢谢。不需要新的 APIClient,因为 APITestCase 已经有一个。但是HTTP_AUTHORIZATION 成功了!
  • 谢谢!我使用的 django-rest-framework-simplejwt 也是如此。使用client.credentials(HTTP_AUTHORIZATION='Bearer <token>' ...(而不是Authorization='Bearer <token>'...,一切正常!
  • 使用正确的用户名/密码,resp = self.client.post(url, {'username':'user@foo.com', 'password':'pass'}, format='json') 出现 401 错误。我收到 Postman 的 200 响应,但此代码收到 401。
  • 当我每天运行单元测试时,代码仍然有效。你应该在你的代码中发布一个独立的问题,让别人帮助你
  • 我没有让它工作,因为我使用了 user_model.objects.create 而不是 user_model.objects.create_user,以防有人发生这种情况。主要区别在于 create_user 在保存之前对密码进行哈希处理。
【解决方案2】:

如果您使用的是 Simple JWTpytest 以及 Python 3.6+,则以下答案适用。你需要创建一个fixture,我称之为api_client,你需要为现有用户获取token。

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

请注意,在上面的夹具中,用户是在那里创建的,但是您可以使用另一个夹具来创建用户并将其传递给这个用户。关键元素是以下行:

refresh = RefreshToken.for_user(user)

此行允许您按照in the docs 的说明手动创建令牌。获得该令牌后,您可以使用方法credentials 来设置标头,然后将包含在测试客户端的所有后续请求中。请注意,refresh.access_token 包含访问令牌。

这个fixture必须用在你的测试中,你要求用户被认证,如下例所示:

@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

【讨论】:

  • 为我完成了工作,正是我需要的。谢谢!
【解决方案3】:

我有类似的问题,附上我给你我的解决方案只是为了有更多的代码来比较(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)

【讨论】:

    【解决方案4】:

    受@dkarchmer 启发,这是我的代码。
    我正在使用电子邮件用于身份验证的自定义用户模型。
    请注意使用email 字段进行身份验证请求。 如果我使用username,则响应为400_BAD_REQUEST401_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)
    

    【讨论】:

    • 希望这可以帮助@alexei-marinichenko
    【解决方案5】:

    Postman 与您的实际数据库进行交互。 Django 使用单独的数据库来运行它的测试用例。因此,在进行身份验证测试之前,需要在测试定义中再次创建新的用户记录。希望这会有所帮助。

    【讨论】:

    • 用户在test_get_member()的第一行创建
    • 谢谢@Christof。确保密码被加密以防万一。从记忆中,我遇到了这两个问题。祝你好运。
    【解决方案6】:

    好吧,因为我使用的是 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)
    
            refresh = RefreshToken.for_user(user)
            return {"HTTP_AUTHORIZATION":f'Bearer {refresh.access_token}'}
    

    在我的 django 单元测试中:

    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)
    
    

    【讨论】:

    • 但是这里的client = APIClient()没有用
    • 我收到此错误:ValueError: invalid literal for int() with base 10
    • @ashdaily 如何导入函数bearer_token
    • @EliasPrado 我已经更新了答案,它是self.bearer_token 而不是bearer_token
    猜你喜欢
    • 1970-01-01
    • 2015-09-25
    • 2015-06-01
    • 2015-05-12
    • 1970-01-01
    • 2020-08-04
    • 1970-01-01
    • 2014-09-14
    • 2016-12-12
    相关资源
    最近更新 更多