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

Django单元测试中BadHeaderError触发失败,返回200而非预期400

Django密码重置功能BadHeaderError测试失败排查

问题背景

在Django中实现密码重置功能时,设定发送邮件触发BadHeaderError时返回提示HttpResponse,但编写单元测试验证该异常场景时,接口返回200状态码而非预期的400,测试失败。

相关代码

重置密码视图代码

class ResetPassword(View):
    form_class = ForgetPasswordForm()
    template_name = 'authentication/password_reset.html'
    def get(self, request):
        form = self.form_class
        return render(request, self.template_name, {'form': form})

    def post(self, request):
        msg = None
        form = ForgetPasswordForm(request.POST)
        if form.is_valid():
            data = form.cleaned_data.get('email')
            associated_users = Users.objects.filter(Q(email = data))
            if associated_users.exists():
                for user in associated_users:
                    subject = 'Password Reset Requested'
                    email_template_name = "authentication/password_reset_email.txt"
                    c = {
                        "email": user.email,
                        'domain': '127.0.0.1:8000',
                        'site_name': 'ATB Project Organizer',
                        'uid': urlsafe_base64_encode(force_bytes(user.pk)),
                        'user': user,
                        'token': default_token_generator.make_token(user),
                        'protocol': 'http',
                    }
                    email = render_to_string(email_template_name, c)
                    try:
                        send_mail(subject, email, 'admin@example.com', [user.email], fail_silently=False)
                    except BadHeaderError:
                        return HttpResponse('Invalid header found')
                    msg = 'You have successfully reset your password!'
                    return redirect('/password_reset/done/')
            else:
                msg = 'No records found for this email, please make sure you have entered the correct email address!'
        form = ForgetPasswordForm()
        return render(request, 'authentication/password_reset.html', {'form': form, 'msg': msg})

测试代码

from django.test import TestCase
from django.urls import reverse
from django.core import mail
from django.contrib.auth import get_user_model

User = get_user_model()

class PasswordResetTest(TestCase):
    def setUp(self):
        self.user1 = User.objects.create_user("user1", email = "user1@mail.com", password = "password@121", orcid = '1234567890987654')
        self.user2 = User.objects.create_user("user2", email = "user2@mail.com", password = "password@122", orcid = '1234567890987655')
        self.user3 = User.objects.create_user("user3@mail.com", email = "not-that-mail@mail.com", password = "password@123", orcid = '1234567890987656')
        self.user4 = User.objects.create_user("user4", email = "user4@mail.com", password = "passs", orcid = '1234567890987657')
        self.user5 = User.objects.create_user("user5", email = "uѕer5@mail.com", password = "password@125", orcid = '1234567890987658')  # email contains kyrillic s

        self.user={
            'username':'username',
            'email':'testemail@gmail.com',
            'password1':'password@123',
            'password2':'password@123',
            'orcid': '1234123412341239',
        }

    def test_user_can_resetpassword(self):
        response = self.client.get(reverse('resetpassword'))
        self.assertEqual(response.status_code, 200)
        self.assertTemplateUsed(response, 'authentication/password_reset.html')


        # Then test that the user doesn't have an "email address" and so is not registered
        response = self.client.post(reverse('resetpassword'), {'email': 'admin@example.com'}, follow=True)
        self.assertEqual(response.status_code, 200)
       
        # Then post the response with our "email address"
        response = self.client.post(reverse('resetpassword'),{'email':'user1@mail.com'})
        self.assertEqual(response.status_code, 302)
        # At this point the system will "send" us an email. We can "check" it thusly:
        self.assertEqual(len(mail.outbox), 1)
        self.assertEqual(mail.outbox[0].subject, 'Password Reset Requested')
    
    def test_exception_raised(self):
        # verify the email with wrong data
        data = {"uid": "wrong-uid", "token": "wrong-token"}
        response = self.client.post(reverse('resetpassword'), data, format='text/html')
        self.assertEqual(response.status_code, 400)

错误信息

File "../tests/test_passwordreset.py", line 55, in test_exception_raised
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
AssertionError: 200 != 400

测试失败原因

  • 测试数据未通过表单验证:当前测试提交的uid和token并非ForgetPasswordForm的字段,表单校验失败后直接跳过发送邮件的逻辑,返回渲染模板的200响应,根本未进入可能触发BadHeaderError的代码段。
  • 异常触发条件未满足:BadHeaderError仅在邮件头(主题、发件人、收件人等)包含换行符(\n/\r)时抛出,当前视图的邮件参数均为固定合法值,不会触发该异常;同时原视图捕获异常后返回的HttpResponse默认状态码为200,不符合预期的400。

正确触发BadHeaderError的测试方案

1. 修正视图的响应状态码

首先修改视图中捕获异常后的响应,设置正确的400状态码:

except BadHeaderError:
    return HttpResponse('Invalid header found', status=400)

2. 构造有效测试场景(推荐使用Mock)

通过mock模块模拟send_mail抛出BadHeaderError,确保测试逻辑走到异常捕获分支:

from django.test import TestCase, mock
from django.urls import reverse
from django.core.mail import BadHeaderError
from django.contrib.auth import get_user_model

User = get_user_model()

class PasswordResetTest(TestCase):
    # 保留原setUp和其他测试方法...

    def test_exception_raised(self):
        # 创建存在的用户,确保表单验证通过
        User.objects.create_user(email="test@example.com", username="testuser", password="testpass")
        
        # 模拟send_mail抛出BadHeaderError
        with mock.patch('django.core.mail.send_mail') as mock_send_mail:
            mock_send_mail.side_effect = BadHeaderError
            
            # 提交有效email,触发邮件发送逻辑
            response = self.client.post(reverse('resetpassword'), {'email': 'test@example.com'})
            
            # 验证响应状态码和内容
            self.assertEqual(response.status_code, 400)
            self.assertContains(response, 'Invalid header found')

备选方案:构造非法邮件参数触发异常

如果不想使用Mock,可以构造包含换行符的邮件主题或收件人(需确保表单允许此类输入):

def test_exception_raised_with_invalid_header(self):
    # 创建email含换行符的用户(需临时调整表单校验规则允许此类输入)
    user = User.objects.create_user(email="test@example.com\ninvalid", username="testuser", password="testpass")
    
    # 提交该用户的email,触发邮件发送时抛出异常
    response = self.client.post(reverse('resetpassword'), {'email': user.email})
    
    self.assertEqual(response.status_code, 400)

此方案依赖表单校验规则,Mock方式更稳定可控。

内容的提问来源于stack exchange,提问作者Namwanza Ronald

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.05 16:10:29