【问题标题】:How to write tests for django form views?如何为 django 表单视图编写测试?
【发布时间】:2018-05-10 17:45:12
【问题描述】:

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'),

测试类父级:

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 方法

更新:

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

随着我做更多的研究,我认为我将 Django v1.10 升级到 v1.11 会更好,因为我需要重写整个事情,这可能最终导致对这个问题的需求无效。

【问题讨论】:

  • 嘿,@KimStacks 我想知道,这些答案对你有帮助吗?
  • 嗨@JohnMoutafis 抱歉一直忙于其他任务,所以没有时间测试答案。周末会做。
  • @JohnMoutafis 失败的真正原因与用户登录无关,但不知何故,PasswordTokenGenerator 中固有的 check_token 方法未能通过我的测试。随着我做更多的研究,我认为我将 Django v1.10 升级到 v1.11 会更好,我需要重写整个事情,这可能最终导致对这个问题的需求无效。
  • check_token 方法失败是什么意思? @KimStacks 的错误是什么?

标签: python django django-forms


【解决方案1】:

您的 PasswordResetConfirmViewget 方法正在响应 2 个不同的响应。

重定向

当用户为 None 时,它​​会使用重定向 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)

如果您的请求使用了 follow 参数,则 expected_urltarget_status_code 将是重定向终点的 url 和状态码链。

参考: 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)

【讨论】:

    【解决方案2】:

    问题验尸

    让我们从定义问题开始:

    • 状态 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(...)
    

    【讨论】:

      猜你喜欢
      • 2014-07-14
      • 1970-01-01
      • 2019-01-05
      • 2011-11-10
      • 1970-01-01
      • 2015-01-16
      • 2014-09-14
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多