如何为django表单视图编写测试?

时间:2017-11-27 13:08:22

标签: python django django-forms

Django v1.10

FormView代码:

class PasswordResetConfirmView(FormView):
    template_name = "dashboard/account/reset_password_form.html"
    success_url = '/dashboard/'
    form_class = SetPasswordForm

    def authenticate_password_token(self, request, uidb64=None, token=None, encodedtimestring=None):
        try:
            uid = force_text(urlsafe_base64_decode(uidb64))
            user = User.objects.get(pk=uid)
            timestring = force_text(urlsafe_base64_decode(encodedtimestring))
            timestamp = timeparse(timestring)
            timediff = timezone.now() - timestamp
        except (TypeError, ValueError, OverflowError, User.DoesNotExist):
            user = None
            timediff = None

        if timediff is None or timediff.days < 0 or timediff.days > PASSWORD_RESET_TIMEOUT_DAYS:
            messages.error(request, _(
                'The reset password link is no longer valid.'))
            return None

        if user is None or not default_token_generator.check_token(user, token):
            messages.error(request, _('The reset password link is not valid.'))
            return None

        return user

    def get(self, request, uidb64=None, token=None, encodedtimestring=None, *arg, **kwargs):
        form = self.form_class()
        assert uidb64 is not None and token is not None and encodedtimestring is not None

        user = self.authenticate_password_token(
            request, uidb64, token, encodedtimestring)
        if user is None:
            return redirect(reverse('dashboard-login'))

        return self.render_to_response(self.get_context_data(form=form))

    def post(self, request, uidb64=None, token=None, encodedtimestring=None, *arg, **kwargs):
        form = self.form_class(request.POST)
        assert uidb64 is not None and token is not None and encodedtimestring is not None

        user = self.authenticate_password_token(
            request, uidb64, token, encodedtimestring)
        if user is None:
            return redirect(reverse('dashboard-login'))

        if not form.is_valid():
            return self.form_invalid(form)

        new_password = form.cleaned_data['new_password2']
        try:
            with transaction.atomic():
                user.auth_token.delete()
                Token.objects.create(user=user)
                user.set_password(new_password)
                user.save()
        except:
            messages.error(request, _('Password reset was unsuccessful.'))
            return redirect(reverse('dashboard-login'))

        messages.success(request, _('Password has been reset.'))
        return redirect(reverse('dashboard-login'))

urls.py:

url(r'^(?i)recover/password/(?P<uidb64>[0-9A-Za-z]+)/(?P<token>.+)/(?P<encodedtimestring>.+)/$',
    views.PasswordResetConfirmView.as_view(), name='reset-password-confirm'),

testclass parent:

class BaseApiTest(TestCase):
    def setUp(self):
        superuser = User.objects.create_superuser(
            'test', 'test@api.com', 'testpassword')
        self.factory = RequestFactory()
        self.user = superuser
        self.client.login(username=superuser.username, password='testpassword')

我尝试编写测试用例:

class ResetPasswordEmailTest(BaseApiTest):

    def test_password_reset_form(self):
        """
        Ensure that the authenticate token works
        """
        self.client.logout()
        token = default_token_generator.make_token(self.user)
        uidb64 = force_bytes(self.user.id)
        timenow = force_bytes(timezone.now())
        response = self.client.get(
            reverse('reset-password-confirm',
                    args=[urlsafe_base64_encode(uidb64), token,
                          urlsafe_base64_encode(timenow)]))
        self.assertEqual(response.status_code, status.HTTP_200_OK)

我收到错误消息:

tests/password_tests.py", line 129, in test_password_reset_form
    self.assertEqual(response.status_code, status.HTTP_200_OK)
AssertionError: 302 != 200

我不确定如何编写测试类来测试formview的所有3种方法。我的尝试只是测试get方法

更新:

失败的真正原因与用户登录无关,但check_token中固有的PasswordTokenGenerator方法未能通过我的测试。

当我做更多的研究时,我认为将Django v1.10升级到v1.11会更好,我需要重写整个事情,最终可能会使这个问题无效。

2 个答案:

答案 0 :(得分:3)

PasswordResetConfirmView get 方法正在回复2个不同的回复。

<强>重定向

当用户为“无”时,它正在使用重定向URI

进行响应
if user is None:
   return redirect(reverse('dashboard-login'))
  

因此,在这种情况下,响应的状态代码将是HTTP 302   您被重定向到其他URL。

呈现回复

当有用户时,随响应返回的信息是请求中使用的方法。

return self.render_to_response(self.get_context_data(form=form)) 
  

因此,在这种情况下,响应的状态代码将为 HTTP 200   请求已成功。

解决方案:

为了让您的测试用例通过,您可以使用assertRedirects

def test_password_reset_form(self):
    ...
    # A URL that redirects can be followed to termination.
    response = self.client.get(reverse('reset-password-confirm', args=[...]), follow=True)
    self.assertRedirects(response, reverse('dashboard-login'), status_code=302, target_status_code=200)
    self.assertEqual(len(response.redirect_chain), 2)

如果您的请求使用了关注参数,则 expected_url target_status_code 将成为重定向最终点的网址和状态代码链

  

参考:   https://github.com/django/django/blob/master/tests/test_client/tests.py

或者,您可以创建两个单独的测试用例(当用户登录时,当用户未登录时)并使用 assertEqual

# Test for logged in user.
def test_password_reset_form_for_logged_in_user(self):
   ...
   # do stuff
   self.assertEqual(response.status_code, status.HTTP_200_OK)

# Test if user not logged in.
def test_password_reset_form_if_user_not_logged_in(self):
   ...
   # do stuff
   self.assertEqual(response.status_code, status.HTTP_302_FOUND)

答案 1 :(得分:1)

问题 Post Mortem

让我们从定义问题开始:

  • 状态302:

      

    请求的资源暂时驻留在不同的URI下。 - source

    因此,我们假设您正在重定向,因此测试失败。

  • 发生这种情况:

    get()方法中,我们找到了以下代码:

    if user is None:
        return redirect(reverse('dashboard-login'))
    

    因此,如果用户为Anonymous(未经身份验证),则视图会将其重定向到登录页面。

从上面可以看出,当您向PasswordResetConfirmView.get()发出未经身份验证的请求时,就会出现问题。

<强>解决方案:

在提出请求之前,您需要对用户进行身份验证(登录) 您可以使用force_login()方法执行此操作:

  

如果您的网站使用Django的身份验证系统,您可以使用force_login()方法来模拟用户登录网站的效果。当测试要求用户登录时,请使用此方法代替login(),并且用户登录方式的详细信息并不重要。

def my_test_case():
    ... Do stuff ...
    # Login
    self.client.force_login(self.user)
    # Make the request
    response = self.client.get(...)