PasswordResetConfirmViewのテスト

目的

今回パスワード忘れた時の再設定機能に使うviewのテストを作りました。 と言ってもDjangoには既にPasswordResetConfirmViewがあり、 ほとんど自前で書くところはなかったので、getとpost時の挙動だけを確認します。

甘く見ていたら意外と詰まるところが多かったです。

テスト対象のview

# views.py
class PasswordResetConfirm(PasswordResetConfirmView):
    form_class = MySetPasswordForm
    template_name = 'users/password_reset_confirm.html'
    success_url = reverse_lazy('users:password_reset_complete')

MySetPasswordFormはDjangoのSetPasswordFormをBootstrapに対応しただけの form。

テスト

Getのテスト

class PasswordResetConfirm(TestCase):
    """パスワード再入力viewのテスト"""
    def setUp(self):
        """ユーザーを登録し、uidとtokenを準備"""
        from django.contrib.auth import get_user_model
        User = get_user_model()
        user = User.objects.create_user(
            email='test@test.com',
            password='test_password',
        )
        user.save()

        self.uid = urlsafe_base64_encode(force_bytes(user.pk))
        self.token = default_token_generator.make_token(user)

    def test_get(self):
        """getリクエスト"""
        url = reverse(
            'users:password_reset_confirm',
            kwargs={
                'uidb64': self.uid,
                'token': self.token,
            }
        )
        response = self.client.get(url)

        self.assertEqual(response.status_code, 200)
        self.assertTemplateUsed(response,'users/password_reset_confirm.html')

上記はステータスのAssertionErrorが起きてしまいます。 理由は、PasswordResetConfirmViewの内部でtokenをset-passwordに変えてリダイレクトしてるからです。

修正後のテストコード。


class PasswordResetConfirm(TestCase):
    """パスワード再入力viewのテスト"""
    def setUp(self):
        """ユーザーを登録し、uidとtokenを準備"""
        from django.contrib.auth import get_user_model
        User = get_user_model()
        user = User.objects.create_user(
            email='test@test.com',
            password='test_password',
        )
        user.save()

        self.uid = urlsafe_base64_encode(force_bytes(user.pk))
        self.token = default_token_generator.make_token(user)

    def test_get(self):
        """getリクエスト"""
        url = reverse(
            'users:password_reset_confirm',
            kwargs={
                'uidb64': self.uid,
                'token': self.token,
            }
        )
        response = self.client.get(url)

        # getリクエスト時、
        # token部分をset-passwordに置き換えて
        # リダイレクトされることの確認
        # ステータス302
        self.assertEqual(response.status_code, 302)
        # リダイレクトpassword_reset_confirm.html
        redirect_url = reverse(
            'users:password_reset_confirm',
            kwargs={
                'uidb64': self.uid,
                'token': 'set-password',
            }
        )
        self.assertRedirects(
            response,
            redirect_url
        )

Postのテスト

class PasswordResetConfirm(TestCase):
    """パスワード再入力viewのテスト"""
    # setUp()
    # test_get()

    def test_post(self):
        """postリクエスト"""

        data = {
            'password1': 'test_password2',
            'password2': 'test_password2',
        }
        post_url = reverse(
            'users:password_reset_confirm',
            kwargs={
                'uidb64': self.uid,
                'token': 'set-password',
            }
        )
        response = self.client.post(post_url, data=data)

        self.assertEqual(response.status_code, 302)
        self.assertRedirects(
            response,
            reverse('users:password_reset_complete')
        )

上記もまたAssertionErrorが起きてしまいます。 理由は最初のget時にトークンをセッションに保存しているためで、 このテストはいきなりpostしてしまっているからです。

# 修正後
class PasswordResetConfirm(TestCase):
    """パスワード再入力viewのテスト"""
    # setUp()
    # test_get()

    def test_post(self):
        """postリクエスト"""

        # PasswordResetConfirmViewはget時にトークンをsessionに保存し、
        # post時にsessionのトークンをチェックしているため
        # 初めにgetリクエストを投げる
        get_url = reverse(
            'users:password_reset_confirm',
            kwargs={
                'uidb64': self.uid,
                'token': self.token,
            }
        )
        self.client.get(get_url)

        # 次にpostを投げる
        data = {
            'password1': 'test_password2',
            'password2': 'test_password2',
        }
        post_url = reverse(
            'users:password_reset_confirm',
            kwargs={
                'uidb64': self.uid,
                'token': 'set-password',
            }
        )
        response = self.client.post(post_url, data=data)

        self.assertEqual(response.status_code, 302)
        self.assertRedirects(
            response,
            reverse('users:password_reset_complete')
        )

まだAssertionErrorが起きてしまいます。 今回はpostで送るデータが問題で、SetPasswordFormのフィールドは password1 ではなく、new_password1 を使っています。

修正後のテストコードが以下です。


# 2度目の修正後
class PasswordResetConfirm(TestCase):
    """パスワード再入力viewのテスト"""
    # setUp()
    # test_get()

    def test_post(self):
        """postリクエスト"""

        # PasswordResetConfirmViewはget時にトークンをsessionに保存し、
        # post時にsessionのトークンをチェックしているため
        # 初めにgetリクエストを投げる
        get_url = reverse(
            'users:password_reset_confirm',
            kwargs={
                'uidb64': self.uid,
                'token': self.token,
            }
        )
        self.client.get(get_url)

        # 次にpostを投げる
        data = {
            'new_password1': 'test_password2',
            'new_password2': 'test_password2',
        }
        post_url = reverse(
            'users:password_reset_confirm',
            kwargs={
                'uidb64': self.uid,
                'token': 'set-password',
            }
        )
        response = self.client.post(post_url, data=data)

        self.assertEqual(response.status_code, 302)
        self.assertRedirects(
            response,
            reverse('users:password_reset_complete')
        )

これでやっと、getとpostが期待通りに動くことの確認ができました。

まとめ

Djangoに頼りすぎて、テストが全然かけていなかったですが、 Djangoの実際のソースコードを見ることが多く、良い経験になりましたし、 すごい重要だということが今回分かりました。

今回のアプリ作成で初めてテストを書き始めたので、 そもそもテストの仕方が良くないところがあると思いますが、 自分と同じようにPasswordResetConfirmViewのテストをしたい方の参考になれば幸いです。