ここ2週間ほど夏風邪でお粥とうどんしか食べていなかったのに、なぜか体重が2キロ増えた津田(id:YTsuda)です。 フロントエンドを中心に開発全般何でもやっています。
今回はハマりどころの多い CakePHPのControllerのテストについて書きます。
環境
CakePHP2系で、PHPUnitを使ってテストをしているという前提で進めます。
redirect がスルーされる問題
Controller のテストを実行する時に、$this->redirect() がスルーされてしまう、という問題があります。 公式ドキュメントにも以下のような記述があります。
<?php class ArticlesController extends AppController { public function add() { if ($this->request->is('post')) { if ($this->Article->save($this->request->data)) { $this->redirect( array('action' => 'index') ); } } // more code } }
上記のコードをテストすると、リダイレクトに到達したにもかかわらず // more code が 実行されてしまいます。
引用: コントローラーのテスト :: テスト — CakePHP Cookbook 2.x ドキュメント
そして、同ドキュメントでは // more code が実行されないようにする方法として、 以下のように、return を推奨しています。
<?php class ArticlesController extends AppController { public function add() { if ($this->request->is('post')) { if ($this->Article->save($this->request->data)) { // ここで return する return $this->redirect( array('action' => 'index') ); } } // more code } }
しかし、この方法では解決できないケースもあります。 例えば、別に切り分けたメソッドの中で redirect をしているケースです。
<?php class ExamplesController extends AppController { public function add() { $this->_redirectMethod(); // メソッド内でリダイレクト // more code } // リダイレクトさせるメソッド(適当) private function _redirectMethod(){ return $this->redirect('/'); // return しても add() に戻るだけ } }
この場合 _redirectMethod で return しても、addメソッドに戻るだけなので // more code は実行されてしまいます。 このケースでは元コードを構えば楽に対応できますが、Component で redirect を使う場合も同様の問題をはらんでいます。 そのため、今回は根本的な解決方法を探ってみました。
解決法
今回は、redirect をさらにモックして Exception 投げるようにしてみました。
<?php class ExamplesControllerTest extends ControllerTestCase { public function testAddRedirect(){ $this->Controller = $this->generate('Examples', array( 'methods' => array('redirect') )); $this->Controller->expects($this->any()) ->method('redirect') ->will( $this->throwException(new ForbiddenException('error'))); $this->setExpectedException('ForbiddenException'); $this->testAction('/examples/add' ); } }
これで期待どおり ForbiddenException が発生し、テストが成功しているはずです。 順を追って説明します
1. Controllerのモックを作る
まず Controller 全体をモックしています 。
<?php $this->Controller = $this->generate('Examples', array( 'methods' => array('redirect') ));
参考: テストアクションによるモックの使用 :: テスト — CakePHP Cookbook 2.x ドキュメント
2. モックの挙動を設定
さらに redirect が ForbiddenException を投げるようにモックの挙動を設定します。もちろんForbiddenでなくても結構です。
<?php $this->Controller->expects($this->any()) ->method('redirect') ->will($this->throwException(new ForbiddenException('error')));
参考: PHPUnit マニュアル – 第10章 テストダブル
3. assertion
そして、ForbiddenException が起こることを期待する assertion です。
<?php $this->setExpectedException('ForbiddenException');
参考: 例 4.11: テスト対象のコードで発生するであろう例外の指定 :: PHPUnit マニュアル – 第4章 PHPUnit 用のテストの書き方
4. getリクエストのテストを実行
最後に testAction で get リクエストのテストを走らせます。
<?php $this->testAction('/examples/add' );
参考: GETリクエストのシミュレート :: テスト — CakePHP Cookbook 2.x ドキュメント
おわり!
Controllerのテストを書くのは大変で、ハマりどころも多いですが、(ACLなど)ミスがあったら致命的な問題が発生する箇所でもあります。 めんどくさがらずに書いていきましょう。