Django DRF密码重置视图单元测试失败,如何修正测试代码?
解决Django DRF PasswordResetView单元测试中密码未更新的问题
我之前也踩过一模一样的坑!手动测试流程丝滑得很,一跑自动化就掉链子,那种明明返回200但密码纹丝不动的挫败感太懂了。咱们一步步拆解问题,找到根源解决它:
最常见的几个坑及修复方案
1. 测试里没重新从数据库拉取用户对象
这绝对是Top1的问题!很多人会犯的错:创建用户后存了个self.user变量,POST新密码后直接用这个变量去验证密码——但这个变量是内存里的旧对象,根本没同步数据库的更新!
修复代码:
# ❌ 错误:用内存里的旧对象验证 # assert self.user.check_password('new_pass123') == True # ✅ 正确:重新从数据库获取或者刷新对象 user = User.objects.get(pk=self.user.pk) # 或者用refresh_from_db() self.user.refresh_from_db() assert self.user.check_password('new_pass123') == True
2. 密码重置请求的参数名不对
DRF的密码重置确认视图(不管是自带的还是自定义的),要求的密码参数是new_password1和new_password2,不是password或者其他自定义名称!如果参数名错了,视图会默默忽略,返回200但啥也没改。
正确的POST请求示例:
response = self.client.post( reset_confirm_url, data={ 'new_password1': 'new_secure_pass123', 'new_password2': 'new_secure_pass123' }, format='json' # DRF视图记得加这个格式参数 )
3. 邮件里的token/uid提取错误
如果从邮件里解析的重置URL不对,或者token/uid提取错了,视图会通过token验证(因为DRF默认返回200,但实际没执行密码更新)。建议加一步token有效性验证,提前排查问题:
提取并验证token的代码:
import re from django.utils.http import urlsafe_base64_decode from django.contrib.auth.tokens import default_token_generator # 从邮件内容里抠出重置URL email_content = mail.outbox[0].body reset_url_match = re.search(r'http://testserver(/reset/[^/]+/[^/]+/)', email_content) reset_confirm_url = reset_url_match.group(1) # 拆分出uidb64和token,验证有效性 uidb64 = reset_confirm_url.split('/')[2] token = reset_confirm_url.split('/')[3] user = User.objects.get(pk=urlsafe_base64_decode(uidb64).decode()) assert default_token_generator.check_token(user, token), "提取的Token无效!"
4. 自定义视图/序列化器里漏了保存用户
如果是自定义的PasswordResetConfirmView或者序列化器,一定要确保在验证通过后调用了user.save()!比如序列化器的save方法里:
正确的序列化器示例:
class PasswordResetConfirmSerializer(serializers.Serializer): new_password1 = serializers.CharField(max_length=128) new_password2 = serializers.CharField(max_length=128) uid = serializers.CharField() token = serializers.CharField() def validate(self, attrs): # 省略uid和token的验证逻辑... pass def save(self): password = self.validated_data['new_password1'] self.user.set_password(password) self.user.save() # 这行绝对不能忘! return self.user
5. 测试事务的影响(少见但可能)
如果你的测试类继承的是TestCase,Django会在每个测试方法后回滚事务,但同一个测试方法内的数据库操作是可见的。如果还是有问题,可以试试换成TransactionTestCase(它会在测试后截断表,而非回滚)。
完整的测试用例参考
把上面的坑都避开后,完整的测试流程应该是这样的:
from django.test import TestCase from django.contrib.auth import get_user_model from django.core import mail from rest_framework.test import APIClient import re from django.utils.http import urlsafe_base64_decode from django.contrib.auth.tokens import default_token_generator User = get_user_model() class PasswordResetFlowTest(TestCase): def setUp(self): self.client = APIClient() self.user = User.objects.create_user( username='test_user', email='test@example.com', password='old_pass123' ) def test_full_password_reset_flow(self): # 1. 发送重置邮件 response = self.client.post( '/api/password_reset/', data={'email': 'test@example.com'}, format='json' ) self.assertEqual(response.status_code, 200) self.assertEqual(len(mail.outbox), 1) # 2. 提取并验证重置URL email_content = mail.outbox[0].body reset_url_match = re.search(r'http://testserver(/api/reset/[^/]+/[^/]+/)', email_content) self.assertIsNotNone(reset_url_match) reset_confirm_url = reset_url_match.group(1) # 额外验证token有效性 uidb64 = reset_confirm_url.split('/')[3] token = reset_confirm_url.split('/')[4] user = User.objects.get(pk=urlsafe_base64_decode(uidb64).decode()) self.assertTrue(default_token_generator.check_token(user, token)) # 3. 提交新密码 new_password = 'new_secure_pass_2024' response = self.client.post( reset_confirm_url, data={ 'new_password1': new_password, 'new_password2': new_password }, format='json' ) self.assertEqual(response.status_code, 200) # 4. 验证密码已更新 self.user.refresh_from_db() self.assertTrue(self.user.check_password(new_password))
内容的提问来源于stack exchange,提问作者J. Hesters
相关产品推荐
相关产品推荐

