CakePHP3でControllerのユニットテストを書く例

Controllerのテスト例を書きます。
`bin/cake bake test Controller ArticlesController`とかのbakeでファイルが生成される(Controllerのbakeでも一緒にできているはず)ので、それをベースに編集したらいいと思います。
メソッド名は『test』で始まっていれば何でも大丈夫です。(わかりやすい名前がいいと思うけど)

参考：
[Cookbook 3.x テスト](http://book.cakephp.org/3.0/ja/development/testing.html)
[PHPUnit 用のテストの書き方 - アサーション](https://phpunit.de/manual/3.7/ja/writing-tests-for-phpunit.html#writing-tests-for-phpunit.assertions)

# 表示できるかのテスト

まずは表示できないと始まりません。

```php:tests/TestCase/Controller/ArticlesControllerTest.php
public function testIndex()
{
    $this->get('/articles');
    $this->assertResponseOk();
    $this->assertResponseContains('>ログイン</a>');
    $this->assertCount(10, $this->viewVariable('articles'));
}
```

/articlesへのGETリクエストで2xxのレスポンスが返ってくることをテストしています。
`get()`以外に、`post()`、`put()`、`delete()`、`patch()`もできます。

`assertResponseContains()`で、レスポンス内容に『`>ログイン</a>`』が含まれていることなどもテストできます。
`$this->viewVariable()`によりControllerで`set()`した値が取れるので、正しく値がセットされているか等がテストできます。

# 表示がエラーになることのテスト

無効なパラメータや不正なアクセスの時に例外を投げる処理がある場合、それをテストします。

```php:tests/TestCase/Controller/ArticlesControllerTest.php
public function testViewInvalid()
{
    $this->get('/articles/view/hoge');
    $this->assertResponseError();

    // または以下でも可
    $this->setExpectedException('Cake\Network\Exception\NotFoundException');
    $this->get('/articles/view/hoge');
}
```

4xx(NotFound等)のレスポンスをテストする`$this->assertResponseError()`でエラーになることをテストします。
また、期待する例外(というのも変だけど)を`setExpectedException()`で指定して`get()`などの前に書けば、特定のExceptionが発生していることをテストすることができます。

# データを登録するテスト

フォームに何かを入力してDBに保存できることをテストします。

```php:tests/TestCase/Controller/ArticlesControllerTest.php
public $fixtures = [
    'app.articles',
]

private $__initialCount = null;

public function setUp()
{
    parent::setUp();
    $this->articles = TableRegistry::get('Articles');
    $this->__initialCount = $this->articles->find()->count();
}

public function tearDown()
{
    parent::tearDown();
    unset($this->articles);
    $this->__initialCount = null;
}

public function testAddPost()
{
    $data = [
        'title' => 'TEST title',
        'start_date' => [
            'year' => '2016',
            'month' => '03',
            'day' => '24',
        ]
    ];
    $this->post('/articles/add', $data);
    $this->assertRedirect(['action' => 'index']);
    $this->assertEquals($this->__initialCount + 1, $this->articles->find()->count());
}
```

`$data`の内容をPOSTし、indexにリダイレクトされることを`assertRedirect()`でテストします。
またDBのレコードが1つ増えていることをテストします。

# バリデーションエラーになることのテスト

同様に、データをPOSTした時にバリデーションに引っかかってDBに保存できないことをテストします。

```php:tests/TestCase/Controller/ArticlesControllerTest.php
public function testAddPostError()
{
    $data = [
        'title' => ''
    ];
    $this->post('/articles/add', $data);
    $this->assertNoRedirect();
    $this->assertNotEmpty($this->viewVariable('article')->errors());
    $this->assertArrayHasKey('title', $this->viewVariable('article')->errors());
    $this->assertEquals($this->__initialCount, $this->articles->find()->count());
}
```

`$this->viewVariable()`でテンプレートにアサインされた(Controllerで`$this->set()`された)変数が取れるので、バリデーションエラーがある場合にセットされている`errors()`が空じゃないこと、titleにエラーがあること等をテストします。
リダイレクトがなく、DBにもレコードが増えていないことをテストします。

# ログインが必要なページのテスト

ログイン認証が必要なページは、セッションにログインしていることにしたい人の情報を入れることでログイン済みとしてアクセスさせることができます。

```php:tests/TestCase/Controller/ArticlesControllerTest.php
public function testMypage()
{
    $this->session(['Auth' => [
        'User' => [
            'id' => 1,
            'email' => 'test1@example.com',
            'name' => '山田太郎',
            ...
        ]
    ]]);
    $this->get('/mypage');
    $this->assertResponseOk();
    $this->assertResponseContains('山田太郎さんこんにちは');
}
```

Cookieも`$this->cookie('name', 'value')`でセットすることができます。

# 送信するリクエストを設定してテスト

headerや`$this->request->env()`で取得できるような値も`$this->configRequest()`で設定することができます。
`$this->request->clientIp()`を見てアクセス制限をしているようなアプリケーションもテストすることができます。

```php:tests/TestCase/Controller/ArticlesControllerTest.php
public function testIpAccess()
{
    $this->configRequest([
        'headers' => ['CLIENT_IP' => '12.34.56.78'],
        'environment' => ['REMOTE_ADDR' => '23.45.67.89']
    ]);
    $this->get('/limitedpage');
    $this->assertResponseOk();
}
```

`headers`に指定した値は、頭に『HTTP_』が付く値(上記例ならHTTP_CLIENT_IP)として設定されます。(Controller内の$this->request->env('HTTP_CLIENT_IP')で'12.34.56.78'が返るようになります。)