PasswordResetConfirmViewのテスト

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

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

## テスト対象のview
```python
# 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のテスト
```python
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に変えてリダイレクトしてるからです。

修正後のテストコード。

```python

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のテスト

```python
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してしまっているからです。


```python
# 修正後
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` を使っています。

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

```python

# ２度目の修正後
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のテストをしたい方の参考になれば幸いです。