第2章 タスク詳細表示
04.エラー処理がちょっと気になる
賢明なみなさんであれば、そろそろ「エラー処理」が気になってくるのではないだろうか。 この点についても、先に進む前に確かめておきたい。
今回は、app/Http/Controllers/TaskController.php
で修正したdetail()
の中に、
問題が起きそうな箇所がある。
public function detail(int $id)
{
$task = Task::find($id);
return view('tasks.detail', ['task' => $task]);
}
この3行目、$id
を使ってタスクの検索しているが、「指定したIDが存在しない」ということもありえる。
(URLの一部を$id
として使うので、存在しないIDを手動で指定可能だ。)
存在しないIDが指定された場合のテスト
指定したIDが存在しない場合には、リソースが存在していないので404
のHTTPステータスコードを返却すべきだ。
まずは、それを確認するテストコードを追加しよう。
tests/Feature/TaskControllerTest.php
を下記のようにする。
<?php
namespace Tests\Feature;
use Tests\TestCase;
use Illuminate\Foundation\Testing\WithFaker;
use Illuminate\Foundation\Testing\RefreshDatabase;
class TaskControllerTest extends TestCase
{
/**
* Get All Tasks Path Test
*
* @return void
*/
public function testGetAllTasksPath()
{
$response = $this->get('/tasks');
$response->assertStatus(200);
}
/**
* Get Task Detail Path Test
*
* @return void
*/
public function testGetTaskPath()
{
$response = $this->get('/tasks/1');
$response->assertStatus(200);
}
/**
* Get Task Detail Path Not Exists Test
*
* @return void
*/
public function testGetTaskPathNotExists()
{
$response = $this->get('/tasks/0');
$response->assertStatus(404);
}
}
testGetTaskPathNotExists()
で、存在しないIDを指定すると404
のステータスコードが返ってくる、
ということを確認している。
一度、テストを実行してみよう。
$ vendor/bin/phpunit
PHPUnit 6.5.8 by Sebastian Bergmann and contributors.
..F.. 5 / 5 (100%)
Time: 996 ms, Memory: 20.00MB
There was 1 failure:
1) Tests\Feature\TaskControllerTest::testGetTaskPathNotExists
Expected status code 404 but received 500.
Failed asserting that false is true.
/Users/[ユーザ名]/task-manager/task-manager/vendor/laravel/framework/src/Illuminate/Foundation/Testing/TestResponse.php:78
/Users/[ユーザ名]/task-manager/task-manager/tests/Feature/TaskControllerTest.php:44
FAILURES!
Tests: 5, Assertions: 9, Failures: 1.
追加したテストが失敗した。
エラーメッセージを読むと、500
のステータスコードが返ってきたことがわかる。
このステータスコードは、サーバの内部エラーなので、つまりプロダクトコードでエラーが発生している、というわけだ。
ログを使って調査
発生箇所はこのメッセージだけだとわかりづらい。
念のためにLaravel
の出力するログファイルにも目を通しておこう。
storage/logs/laravel.log
というファイルに、プロダクトコードでエラーが発生した場合のログが保存されている。
分量は各自の状況で変わるが、ファイル内に下記で始まるメッセージがあるはずだ。
[2018-05-20 13:02:57] testing.ERROR: Trying to get property 'title' of non-object (View: /Users/[ユーザ名]/task-manager/task-manager/resources/views/tasks/detail.blade.php) {"exception":"[object] (ErrorException(code: 0): Trying to get property 'title' of non-object (View: /Users/[ユーザ名]/task-manager/task-manager/resources/views/tasks/detail.blade.php) at /Users/[ユーザ名]/task-manager/task-manager/storage/framework/views/37e261dc716e7ccccdd8961010ed27a327614092.php:31, ErrorException(code: 0): Trying to get property 'title' of non-object at /Users/[ユーザ名]/task-manager/task-manager/storage/framework/views/37e261dc716e7ccccdd8961010ed27a327614092.php:31)
[stacktrace]
#0 /Users/[ユーザ名]/task-manager/task-manager/vendor/laravel/framework/src/Illuminate/View/Engines/PhpEngine.php(45): Illuminate\\View\\Engines\\CompilerEngine->handleViewException(Object(ErrorException), 1)
(略)
これを見ると、ビューのdetail.blade.php
でtitle
を使えなかったよ、というエラーメッセージであることが確認できる。
つまり、「指定したIDが存在しない場合」でもビューの表示に処理が遷移しているが、 しかし実際にはデータが無いので処理できずにエラーが発生した、、、ということがわかる。
ということで、当初考えていたとおり、
指定したIDが存在しない場合に404
のHTTPステータスコードを返却するように実装すれば良さそうだ。
テストを使ってフレームワークの動作確認
では、「指定したIDが存在しない場合」はどうやってわかるだろうか。 今度は、モデルのテストを使って、動作確認コードを作ってみよう。
tests/Unit/TaskTest.php
に、下記のようにコードを追加してみよう。
<?php
namespace Tests\Unit;
use App\Task;
use Tests\TestCase;
use Illuminate\Foundation\Testing\WithFaker;
use Illuminate\Foundation\Testing\RefreshDatabase;
class TaskTest extends TestCase
{
/**
* Get Seeder Tasks Test
*
* @return void
*/
public function testGetSeederTasks()
{
// 全件取得
$tasks = Task::all();
// 3件取得できるはず
$this->assertEquals(3, count($tasks));
// 実行完了していないものを取得
$taskNotFinished = Task::where('executed', false)->get();
// 2件取得できるはず
$this->assertEquals(2, count($taskNotFinished));
// 実行完了しているものを取得
$taskFinished = Task::where('executed', true)->get();
// 1件取得できるはず
$this->assertEquals(1, count($taskFinished));
// 「テストタスク」というタイトルのレコードを取得
$task1 = Task::where('title', "テストタスク")->first();
// 実行完了していないはず
$this->assertFalse(boolval($task1->executed));
// 「終了タスク」というタイトルのレコードを取得
$task1 = Task::where('title', "終了タスク")->first();
// 実行完了しているはず
$this->assertTrue(boolval($task1->executed));
}
public function testGetTaskDetail() {
$tasks = Task::find(2);
$this->assertEquals('テストタスク', $tasks->title);
}
public function testGetTaskDetailNotExists() {
$tasks = Task::find(0);
$this->assertNull($tasks);
}
}
取得できなかったらnull
になるのではないか?という発想で、このようなコードとした。
一般的にはnull
となりがちなので、確かめてみる価値はあるだろう。
今回は動作確認が目的なので、tests/Unit/TaskTest.php
だけテストを実行しよう。
$ vendor/bin/phpunit tests/Unit/TaskTest.php
PHPUnit 6.5.8 by Sebastian Bergmann and contributors.
... 3 / 3 (100%)
Time: 450 ms, Memory: 12.00MB
OK (3 tests, 7 assertions)
お、成功している。つまり、find()
で取得できなかったらnull
になる、
という動作で良いようだ。
コントローラの修正
では、安心してコントローラの修正に取り掛かろう。
app/Http/Controllers/TaskController.php
を、下記のように修正してみる。
<?php
namespace App\Http\Controllers;
use App\Task;
use Illuminate\Http\Request;
class TaskController extends Controller
{
public function index()
{
$tasks = Task::all();
return view('tasks.index', ['tasks' => $tasks]);
}
public function detail(int $id)
{
$task = Task::find($id);
if ($task === null) {
abort(404);
}
return view('tasks.detail', ['task' => $task]);
}
}
find()
の後にチェック処理としてnull
の判定を入れた。
null
だった場合には404
のステータスコードをabort()
で発生させて、
ここで処理が終わるようになっている。
では、テストを実行してみよう。
$ vendor/bin/phpunit
PHPUnit 6.5.8 by Sebastian Bergmann and contributors.
...... 6 / 6 (100%)
Time: 643 ms, Memory: 14.00MB
OK (6 tests, 10 assertions)
問題なくパスするようになった。
エラー処理や例外処理といったものも、気づいた時にテストコードを書いて、 テストをパスするようにプロダクトコードを修正、というリズムで進めることは変わらない。
さらに、今回はフレームワークの仕様(find()
で検索に失敗した場合の返り値)もテストで確認した。
このように「知識の面での動作確認」にもテストを使うことができるわけだ。
少し寄り道が長くなったように感じるかもしれないが、必要なことを確かめるためだ。 気にせず、次に進んでいこう。
次が本章の最後、ページ遷移の実装だ。