You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.27 09:29:15