CakePHP3で認証処理のテストケースをつくる

CakePHP3は公式サイトでテストケースの作成について紹介されています。
そこで認証が必要なページのテストケースについては記述方法が書かれていますが、ログインやログアウトといった処理自体のテストケースについて記載されていないので、今回作ってみます。

# 前提
[クイックスタートガイド](https://book.cakephp.org/3.0/ja/quickstart.html)に従い作られた以下のような認証処理であること。

- Usersはemailカラムをログイン名、passwordカラムをパスワードとする
- パスワードはCakePHP3のデフォルトのbcryptでハッシュ化される
- Usersはemail、password以外にid、created、modifiedカラムを持つ
- ログイン/ログアウトメソッドはUsersControllerに作成される

また、[テスト](https://book.cakephp.org/3.0/ja/development/testing.html)に従い、PHPUnitの実行ができるようになっていること(PHPUnitのインストール、DBの設定等)。

# テストデータの作成
## UsersFixtureの編集
クイックスタートガイドに従って`bake all users`を実行していれば`tests\Fixture\UsersFixture.php`というファイルが存在していると思います。
そのファイルの下のほうにある$recodesを以下のように編集し、テストに使用するデータを定義します。

```php
public $records = [
        [
            'id' => 1,
            'email' => 'test_user',
            'password' => '$2y$10$RDuyVB6AjZBb1eEa0Strwu8raIniMx1rhX5Mdl3KSAvfLw7SGBaHG',
            'created' => '2017-05-21 05:39:51',
            'modified' => '2017-05-21 05:39:51'
        ],
    ];
```

今回は、ユーザー名をtest_user、パスワードをpasswordとしました。
passwordカラムの値はpasswordをbcryptでハッシュ化した後の値です。

# テストケース作成
テストケースは`TestCase\Controller\UsersControllerTest.php`に作成されます。
以下では各テストケースのメソッドだけを示すので、Testファイルに追加してください。

## 表示処理
表示確認はステータスとLoginという文字列が含まれているかどうかを確認しています。

```php
    public function testLogin()
    {
        $this->get('/users/login');
        $this->assertResponseOk(); //ステータスが200系である
        $this->assertResponseContains('Login'); //ボディにLoginという文字列が含まれている
    }
```

## ログイン処理(正常系)
ログイン処理(正常系)のテストでは、DBに登録されているユーザー情報でログイン処理を実行させ、その結果としてセッションにログインユーザーの情報が格納されることを確認します。

```php
use Cake\I18n\FrozenTime; //FrozenTimeを使用する(クラス外で宣言)
    
    public function testLoginOk()
    {
        // ログイン処理の実行
        $this->post('/users/login', [
            'name' => 'test_user',
            'password' => 'password'
        ]);

        // ログイン後のユーザー情報(パスワード以外)
        $user = [
          'id' => 1,
          'name' => 'test_user',
          'created' => new FrozenTime('2017-05-21 05:39:51'),
          'modified' => new FrozenTime('2017-05-21 05:39:51')
        ];
       // セッションのユーザー情報と比較
       $this->assertSession($user, 'Auth.User');
    }
```

DATETIME型のカラムはCakePHPではFrozenTime型のオブジェクトになります。
また、ログイン後にセッションに登録されるユーザー情報にはセキュリティ上パスワードの情報が含まれないのでそれ以外のカラムの値を設定し、assertSessionメソッドで比較します。

## ログイン処理(異常系)
ログイン処理(異常系)の処理では、DBに登録されていないデータを使ってログイン処理を行います。
ログインが失敗した場合はセッションのAuth.Userはnullになります。
また、リダイレクトしないことも確認します。

```php
    public function testLoginFail()
    {
        // ログイン処理の実行
        $this->post('/users/login', [
            'name' => 'wrong-test_user',
            'password' => 'wrong-password'
        ]);
        $this->assertNull($this->_requestSession->read('Auth.User')); // セッションのユーザー情報がNULL
        $this->assertNoRedirect(); //リダイレクトしない
    }
```

## ログアウト処理
ログアウトの処理では、先にセッションにユーザー情報を設定してからログアウト処理を実行します。
ログアウト処理実行後にセッションのユーザー情報がNULLになっていればログアウトが成功しています。

```php
    public function testLogout() {
        // セッションにユーザー情報を設定
        $this->session([
            'Auth' => [
                'User' => [
                    'id' => 1,
                    'name' => 'test_user',
                    'created' => new FrozenTime('2017-05-21 05:39:51'),
                    'modified' => new FrozenTime('2017-05-21 05:39:51')
                ]
            ]
        ]);

        $this->get('/users/logout'); // ログアウトの実行
        $this->assertNull($this->_requestSession->read('Auth.User')); // セッションのユーザー情報がNULL
    }
```

# テストの実行
PHPUnitを実行して、各メソッドで.(テスト成功)が出ていればOKです。
UsersControllerTestデフォルトのテストメソッドが残ってる場合、いくつかIが出ると思います($this->markTestIncompleteを使っているため)

# 参考
[stack overflow:How to test login method using cakePHP 3.0 ?](https://stackoverflow.com/questions/40769697/how-to-test-login-method-using-cakephp-3-0)