今回はパスワードのリセットのテストです。

パスワードのリセットは、以下のステップがあります。

  • パスワードリセットのリクエスト画面へ行き、Eメールアドレスを入力してリセットをリクエスト
  • メールでパスワードリセットのトークン付きのURLを受信
  • パスワードリセット画面へ行きパスワードをリセット

テストケースとして考えられるのは、

  • パスワードリセットリクエスト画面へのアクセスができる
  • 登録された会員のEメールアドレスでパスワードのリセットのリクエストが成功がする
  • 登録されていない会員のEメールアドレスでパスワードのリセットのリクエストが失敗
  • 成功したパスワードのリセットのリクエストで受信したURLでパスワードリセットの画面にアクセスができる
  • その画面でパスワードリセットができる

パスワードリセットリクエスト画面でのテスト

まずは、画面にアクセスできることを確認。

   /**
     * @test
     * パスワードリセットをリクエストする画面の閲覧可能
     */
    public function user_can_view_reset_request()
    {
        $response = $this->get('password/reset');

        $response->assertStatus(200);
    }

そして、画面にEメールアドレスを入力して、成功のメッセージが表示されることを確認。

   /**
     * @test
     * パスワードリセットのリクエスト成功
     */
    public function valid_user_can_request_reset()
    {
        // ユーザーを1つ作成
        $user = factory(User::class)->create();

        // パスワードリセットをリクエスト
        $response = $this->from('password/email')->post('password/email', [
            'email' => $user->email,
        ]);

        // 同画面にリダイレクト
        $response->assertStatus(302);
        $response->assertRedirect('password/email');
        // 成功のメッセージ
        $response->assertSessionHas('status',
            'リクエストを受け付けました。パスワードの再設定方法をメールでお知らせします。');
    }

ここで注目することとして2点。

まず、$this->from('password/email')->postで使用されているfrom()。これがないと、 $response->assertRedirect('password/email');以下のようなエラーとなります。

There was 1 failure:

1) Tests\Feature\ResetPasswordTest::valid_user_can_request_reset
Failed asserting that two strings are equal.
--- Expected
+++ Actual
@@ @@
-'http://localhost/password/email'
+'http://localhost'

この画面では成功時には、back()で前の画面に戻されます。しかし、テストでは前の画面の場所は保存していないので、デフォルトのホーム/になります。password/emailに戻るには、セッションあるいはリファラーで前の画面のURIを記録しておく必要があります。以下のように、TestCase.phpにfrom()の定義をしておきます。

namespace Tests;

use Illuminate\Foundation\Testing\TestCase as BaseTestCase;

abstract class TestCase extends BaseTestCase
{
    use CreatesApplication;

   /**
     * Set the referer header to simulate a previous request.
     *
     * @param  string  $url
     * @return $this
     */
    public function from(string $url)
    {
    	session()->setPreviousUrl(url($url));
        return $this;
    }
}

次に注目してもらいたいのは、成功のメッセージが表示されるかどうかのチェック。これは、セッションにstatusのキーが含まれているかどうかをチェックします。成功時には以下のメソッドでstatusにレスの文字列がセッションを介して保存されます。

    /**
     * Get the response for a successful password reset link.
     *
     * @param  string  $response
     * @return \Illuminate\Http\RedirectResponse
     */
    protected function sendResetLinkResponse($response)
    {
        return back()->with('status', trans($response));
    }

このリクエスト画面で、今度は失敗するケース。単に、存在しない会員のEメールアドレスを入力してみます。
今度は、セッションに含まれるerrorsに、emailのエントリがあるかをチェックして確認です。

   /**
     * @test
     * 存在しないメールアドレスでパスワードリセットのリクエストをして失敗
     */
    public function invalid_user_cannot_request_reset()
    {
        // ユーザーを1つ作成
        $user = factory(User::class)->create();

        // 存在しないユーザーのメールアドレスでパスワードリセットをリクエスト
        $response = $this->from('password/email')->post('password/email', [
            'email' => 'nobody@example.com'
        ]);

        $response->assertStatus(302);
        $response->assertRedirect('password/email');
        // 失敗のエラーメッセージ
        $response->assertSessionHasErrors('email',
            '指定のメールアドレスは見つかりませんでした');
    }

パスワードリセット画面でのテスト

今度はリクエストではなく、実際にパスワードをリセットする画面に関してのテストです。このテストは2つの部分からなります。

  • パスワードリセットをリクエストした後に受信するEメールのURLをもとに、パスワードリセット画面にアクセスできるかを確認
  • その画面でパスワードを更新可能なことを確認

少しコードは長くなりますが、まさしくユーザーが辿るステップをそのままここで実現します。

   /**
     * @test
     * パスワードリセットのトークンでパスワードをリセット
     */
    public function valid_user_can_reset_password()
    {
        Notification::fake();

        // ユーザーを1つ作成
        $user = factory(User::class)->create();

        // パスワードリセットをリクエスト
        $response = $this->post('password/email', [
            'email' => $user->email
        ]);

        // トークンを取得

        $token = '';

        Notification::assertSentTo(
            $user,
            ResetPassword::class,
            function ($notification, $channels) use ($user, &$token) {
                $token = $notification->token;
                return true;
            }
        );

        // パスワードリセットの画面へ
        $response = $this->get('password/reset/'.$token);

        $response->assertStatus(200);

        // パスワードをリセット

        $new = 'reset1111';

        $response = $this->post('password/reset', [
            'email'                 => $user->email,
            'token'                 => $token,
            'password'              => $new,
            'password_confirmation' => $new
        ]);

        // ホームへ遷移
        $response->assertStatus(302);
        $response->assertRedirect('/home');
        // リセット成功のメッセージ
        $response->assertSessionHas('status', 'パスワードはリセットされました!');

        // 認証されていることを確認
        $this->assertTrue(Auth::check());

        // 変更されたパスワードが保存されていることを確認
        $this->assertTrue(Hash::check($new, $user->fresh()->password));
    }

まず、最初パートでは、リクエストで送信されたEメールに含まれるトークン付きのURLを取得する必要あります。それがなければ、リセット画面にアクセスできません。送信したメールをログにしてそれを読み込んで、URLで抽出ということも可能です。しかし、ここではNotification::fakeを利用することによって、綺麗に簡単に、トークンを抽出します。

そして、そのトークンを利用してリセット画面でEメールアドレスを更新します。更新成功後では、

  • リセット成功のメッセージが画面に表示されること
  • ユーザが認証されていること
  • 更新されたパスワードが保存されていること

を確認します。更新されたパスワードを取得するのに、$user->passwordでなく、$user->fresh()->passwordとなっていることに注意してください。fresh()により、$userのオブジェクトの中身が更新されます。

他に考えられるテストは?

以上のテストで十分と思われるかもしれませんが、テストケースは切りがないほど考えられます。例えば、

  • 期限切れのトークンでリセット画面にアクセスしたとき
  • 有効のトークンでアクセスして、トークンを発行したものと違うとEメールアドレスを入力したとき
  • リセットの画面でパスワードと確認パスワードが異なるとき

などなど。

最後に今回のテストのコードは以下から利用可能です。

https://github.com/lotsofbytes/larajapan/blob/5.4-test/tests/Feature/ResetPasswordTest.php

By khino