如何在Django单元测试中Mock JWT令牌认证?
Django单元测试Mock JWT验证异常问题解决
问题描述
在Django中编写单元测试时需要Mock JWT令牌验证,使用以下代码出现异常。怀疑问题在于无法使用decode方法——jwt.encode返回str类型,str没有decode方法,但参考的示例里jwt.encode返回的是bytes类型。
辅助函数
def generate_jwt_token(): client_secret = getattr(settings, "AUTH0_CLIENT_SECRET", None) payload = {'user_id': 1} token = jwt.encode(payload, client_secret, algorithm='HS256') # 注释掉下面一行是因为jwt.encode返回str,str没有decode方法 #return token.decode('utf-8') return token
单元测试
@patch('jwt.decode') def test_post_request_with_valid_data_succeeds(self, mock_decode): payload1 = {'user_id': 1} mock_decode.return_value = payload1 response = self.client.post(self.url, self.dataset, format="multipart", HTTP_AUTHORIZATION=f'Bearer {self.token}')
异常信息
Traceback (most recent call last): File "/usr/local/lib/python3.8/unittest/mock.py", line 1325, in patched return func(*newargs, **newkeywargs) File "/app/apps/datasets/test/test_views.py", line 89, in test_post_request_with_valid_data_succeeds response = self.client.post(self.url, self.dataset, format="multipart", HTTP_AUTHORIZATION=f'Bearer {self.token}') File "/root/.local/lib/python3.8/site-packages/rest_framework/test.py", line 296, in post response = super().post( File "/root/.local/lib/python3.8/site-packages/rest_framework/test.py", line 210, in post return self.generic('POST', path, data, content_type, **extra) File "/root/.local/lib/python3.8/site-packages/rest_framework/test.py", line 234, in generic return super().generic( File "/root/.local/lib/python3.8/site-packages/django/test/client.py", line 473, in generic return self.request(**r) File "/root/.local/lib/python3.8/site-packages/rest_framework/test.py", line 286, in request return super().request(**kwargs) File "/root/.local/lib/python3.8/site-packages/rest_framework/test.py", line 238, in request request = super().request(**kwargs) File "/root/.local/lib/python3.8/site-packages/django/test/client.py", line 719, in request self.check_exception(response) File "/root/.local/lib/python3.8/site-packages/django/test/client.py", line 580, in check_exception raise exc_value File "/root/.local/lib/python3.8/site-packages/django/core/handlers/exception.py", line 47, in inner response = get_response(request) File "/root/.local/lib/python3.8/site-packages/django/core/handlers/base.py", line 181, in _get_response response = wrapped_callback(request, *callback_args, **callback_kwargs) File "/root/.local/lib/python3.8/site-packages/django/views/decorators/csrf.py", line 54, in wrapped_view return view_func(*args, **kwargs) File "/root/.local/lib/python3.8/site-packages/rest_framework/viewsets.py", line 125, in view return self.dispatch(request, *args, **kwargs) File "/root/.local/lib/python3.8/site-packages/rest_framework/views.py", line 509, in dispatch response = self.handle_exception(exc) File "/root/.local/lib/python3.8/site-packages/rest_framework/views.py", line 469, in handle_exception self.raise_uncaught_exception(exc) File "/root/.local/lib/python3.8/site-packages/rest_framework/views.py", line 480, in raise_uncaught_exception raise exc File "/root/.local/lib/python3.8/site-packages/rest_framework/views.py", line 497, in dispatch self.initial(request, *args, **kwargs) File "/root/.local/lib/python3.8/site-packages/rest_framework/views.py", line 414, in initial self.perform_authentication(request) File "/root/.local/lib/python3.8/site-packages/rest_framework/views.py", line 324, in perform_authentication request.user File "/root/.local/lib/python3.8/site-packages/rest_framework/request.py", line 227, in user self._authenticate() File "/root/.local/lib/python3.8/site-packages/rest_framework/request.py", line 380, in _authenticate user_auth_tuple = authenticator.authenticate(self) File "/root/.local/lib/python3.8/site-packages/rest_framework_simplejwt/authentication.py", line 38, in authenticate validated_token = self.get_validated_token(raw_token) File "/root/.local/lib/python3.8/site-packages/rest_framework_simplejwt/authentication.py", line 92, in get_validated_token return AuthToken(raw_token) File "/root/.local/lib/python3.8/site-packages/rest_framework_simplejwt/tokens.py", line 42, in __init__ self.payload = token_backend.decode(token, verify=verify) File "/root/.local/lib/python3.8/site-packages/rest_framework_simplejwt/backends.py", line 136, in decode self.get_verifying_key(token), File "/root/.local/lib/python3.8/site-packages/rest_framework_simplejwt/backends.py", line 99, in get_verifying_key return self.jwks_client.get_signing_key_from_jwt(token).key File "/root/.local/lib/python3.8/site-packages/jwt/jwks_client.py", line 59, in get_signing_key_from_jwt return self.get_signing_key(header.get("kid")) File "/root/.local/lib/python3.8/site-packages/jwt/jwks_client.py", line 50, in get_signing_key raise PyJWKClientError( jwt.exceptions.PyJWKClientError: Unable to find a signing key that matches: "None"
问题分析与解决
1. jwt.encode返回值差异问题
这是PyJWT版本差异导致的:
- PyJWT v1.x版本中,
jwt.encode返回bytes类型,需要decode转成str - PyJWT v2.x版本中,
jwt.encode默认返回str类型,不需要decode
你的代码里已经注释掉了decode行,这部分是正确的。如果要兼容两个版本,可以修改辅助函数:
def generate_jwt_token(): client_secret = getattr(settings, "AUTH0_CLIENT_SECRET", None) payload = {'user_id': 1} token = jwt.encode(payload, client_secret, algorithm='HS256') # 兼容PyJWT v1和v2版本 if isinstance(token, bytes): return token.decode('utf-8') return token
2. Mock失效导致的异常问题
报错的核心原因是你Mock的对象不对——你patch了jwt.decode,但项目中实际用的是rest_framework_simplejwt的认证流程,它内部并没有直接调用jwt.decode,而是通过自身的认证后端处理令牌,所以你的Mock根本没生效,真实的认证流程仍在执行,导致找不到匹配的签名密钥。
解决方案一:Mock正确的认证方法
直接Mockrest_framework_simplejwt认证类的authenticate方法,跳过真实的令牌验证:
from unittest.mock import patch from django.contrib.auth.models import User @patch('rest_framework_simplejwt.authentication.JWTAuthentication.authenticate') def test_post_request_with_valid_data_succeeds(self, mock_authenticate): # 模拟认证通过,返回用户对象 test_user = User.objects.create(username='test_user') mock_authenticate.return_value = (test_user, None) response = self.client.post( self.url, self.dataset, format="multipart", HTTP_AUTHORIZATION=f'Bearer {self.token}' ) # 断言请求成功(根据实际需求调整) self.assertEqual(response.status_code, 201)
解决方案二:生成真实的测试令牌
如果不需要完全Mock,也可以用rest_framework_simplejwt提供的工具生成合法的测试令牌:
from rest_framework_simplejwt.tokens import AccessToken from django.contrib.auth.models import User def setUp(self): super().setUp() # 创建测试用户 self.test_user = User.objects.create(username='test_user') # 生成该用户的合法访问令牌 self.token = AccessToken.for_user(self.test_user)
这样发送请求时,令牌会被正常验证通过,不需要Mock。
内容的提问来源于stack exchange,提问作者Neo
相关产品推荐
相关产品推荐

