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
相关产品推荐
相关产品推荐

