第4章 タスクの追加
04.バリデーションの追加
バリデーションのテストとバリデーション実装
さて、一般的なWebのシステムの場合、入力値のチェック、つまり「バリデーション」を行なう。
今回の場合、タスクの「タイトル」は必須項目だ。 また、長さも1文字以上512文字まで、の範囲となる。 (カラム制限の値)
この制限がきちんと行なわれることで、 「正しい(設計どおりの)データを扱う」ことを保証できることになる。
では、実際にバリデーションが行なわれる前提で、コントローラーのテストを書いてみよう。
下記のような条件で、テストを追加してみる。
title
指定無し (バリデーションエラー)title
の長さ0文字 (バリデーションエラー)title
の長さ512文字 (成功)title
の長さ513文字 (バリデーションエラー)
tests/Feature/TaskControllerTest.php
は、下記のようになる。
<?php
namespace Tests\Feature;
use App\Task;
use Tests\TestCase;
use Illuminate\Foundation\Testing\WithFaker;
use Illuminate\Foundation\Testing\RefreshDatabase;
class TaskControllerTest extends TestCase
{
(略)
/**
* Get New Task Path Test
*
* @return void
*/
public function testGetNewTaskPath()
{
$response = $this->get('/tasks/new');
$response->assertStatus(200);
}
/**
* Post Task Path Test
*
* @return void
*/
public function testPostTaskPath()
{
$data = [
'title' => 'test title',
];
$this->assertDatabaseMissing('tasks', $data);
$response = $this->post('/tasks/', $data);
$response->assertStatus(302)
->assertRedirect('/tasks/');
$this->assertDatabaseHas('tasks', $data);
}
/**
* Post Task Path Test (Without Title)
*
* @return void
*/
public function testPostTaskPathWithoutTitle_failed()
{
$data = [];
$response = $this->from('/tasks/new')
->post('/tasks/', $data);
$response->assertSessionHasErrors(['title' => 'The title field is required.']);
$response->assertStatus(302)
->assertRedirect('/tasks/new');
}
/**
* Post Task Path Test (Empty Title)
*
* @return void
*/
public function testPostTaskPathEmptyTitle_failed()
{
$data = [
'title' => ''
];
$response = $this->from('/tasks/new')
->post('/tasks/', $data);
$response->assertSessionHasErrors(['title' => 'The title field is required.']);
$response->assertStatus(302)
->assertRedirect('/tasks/new');
}
/**
* Post Task Path Test (Max Length)
*
* @return void
*/
public function testPostTaskPathTitleMaxLength()
{
$data = [
'title' => str_random(512)
];
$this->assertDatabaseMissing('tasks', $data);
$response = $this->post('/tasks/', $data);
$response->assertStatus(302)
->assertRedirect('/tasks/');
$this->assertDatabaseHas('tasks', $data);
}
/**
* Post Task Path Test (Max Length + 1)
*
* @return void
*/
public function testPostTaskPathTitleMaxLengthPlus1_failed()
{
$data = [
'title' => str_random(513)
];
$response = $this->from('/tasks/new')
->post('/tasks/', $data);
$response->assertSessionHasErrors(['title' => 'The title may not be greater than 512 characters.']);
$response->assertStatus(302)
->assertRedirect('/tasks/new');
}
}
だいぶ長くなってしまったが、条件通りの実装だ。
失敗する場合のエラーメッセージについては、
resources/lang/en/validation.php
が標準で使用される。
1番目と2番目は必須要素であるtitle
の中身が無いので、required
の条件に引っかかることになる。
同じく4番目は文字数の最大長であるmax
の条件に引っかかる。
required
とmax
の具体的な部分は、後のコントローラーの実装で見ることにして、
今はこの状態でテストを実行してみよう。
$ vendor/bin/phpunit
PHPUnit 6.5.8 by Sebastian Bergmann and contributors.
.......FF.F....... 18 / 18 (100%)
Time: 24.67 seconds, Memory: 28.00MB
There were 3 failures:
1) Tests\Feature\TaskControllerTest::testPostTaskPathWithoutTitle_failed
Session is missing expected key [errors].
Failed asserting that false is true.
/Users/[ユーザ名]/task-manager/task-manager/vendor/laravel/framework/src/Illuminate/Foundation/Testing/TestResponse.php:650
/Users/[ユーザ名]/task-manager/task-manager/vendor/laravel/framework/src/Illuminate/Foundation/Testing/TestResponse.php:688
/Users/[ユーザ名]/task-manager/task-manager/tests/Feature/TaskControllerTest.php:145
2) Tests\Feature\TaskControllerTest::testPostTaskPathEmptyTitle_failed
Session is missing expected key [errors].
Failed asserting that false is true.
/Users/[ユーザ名]/task-manager/task-manager/vendor/laravel/framework/src/Illuminate/Foundation/Testing/TestResponse.php:650
/Users/[ユーザ名]/task-manager/task-manager/vendor/laravel/framework/src/Illuminate/Foundation/Testing/TestResponse.php:688
/Users/[ユーザ名]/task-manager/task-manager/tests/Feature/TaskControllerTest.php:164
3) Tests\Feature\TaskControllerTest::testPostTaskPathTitleMaxLengthPlus1_failed
Session is missing expected key [errors].
Failed asserting that false is true.
/Users/[ユーザ名]/task-manager/task-manager/vendor/laravel/framework/src/Illuminate/Foundation/Testing/TestResponse.php:650
/Users/[ユーザ名]/task-manager/task-manager/vendor/laravel/framework/src/Illuminate/Foundation/Testing/TestResponse.php:688
/Users/[ユーザ名]/task-manager/task-manager/tests/Feature/TaskControllerTest.php:205
FAILURES!
Tests: 18, Assertions: 40, Failures: 3.
今回追加したテストのうち「バリデーションエラー」を想定した3つが、失敗となっている。
この時、Laravelのエラーログを確認すると、きちんとログが出力されている。
一応不正なデータは保存されることは無いが、いわゆる500
エラーとなってしまうわけだ。
では、コントローラーでバリデーションの実装をしよう。 Laravelではいくつか実装方法があるが、 今回はシンプルにコントローラーのメソッド内に処理を記載する方法で実装してみる。
<?php
namespace App\Http\Controllers;
use App\Task;
use Illuminate\Http\Request;
class TaskController extends Controller
{
(略)
public function create(Request $request)
{
$validatedData = $request->validate([
'title' => 'required|max:512',
]);
Task::create(['title' => $request->title, 'executed' => false]);
return redirect('/tasks');
}
}
273行目に、先ほど挙げたrequired
とmax
が出てくる。
「具体的な部分」と書いてみたが、実際にはこれだけだ。
max
の後ろの512
が、制限となる文字数である。
では、これで再びテストを実行してみよう。
$ vendor/bin/phpunit
PHPUnit 6.5.8 by Sebastian Bergmann and contributors.
.................. 18 / 18 (100%)
Time: 20.71 seconds, Memory: 22.00MB
OK (18 tests, 52 assertions)
成功だ。 バリデーションが意図通りに働いていることを確認できた。
ビューでもバリデーションの確認
コントローラーで確認できたが、 バリデーションエラーの場合はビューにエラーメッセージが表示されることになる。
というわけで、ビューの方もテストコードを実装しよう。
今回は、required
の方だけ確認してみよう。
tests/Browser/TaskNewTest.php
を下記のようにする。
<?php
namespace Tests\Browser;
use Tests\DuskTestCase;
use Laravel\Dusk\Browser;
use Illuminate\Foundation\Testing\DatabaseMigrations;
class TaskNewTest extends DuskTestCase
{
/**
* New Task Page Test.
*
* @return void
* @throws \Throwable
*/
public function testShowNew()
{
$this->browse(function (Browser $browser) {
$browser->visit('/tasks/new')
->assertSee('New Task');
});
}
/**
* New Task Page Test (Empty Title).
*
* @return void
* @throws \Throwable
*/
public function testShowNewEmptyTitle_failed()
{
$this->browse(function (Browser $browser) {
$browser->visit('/tasks/new')
->press('登録')
->pause(1000)
->assertPathIs('/tasks/new')
->assertSee('The title field is required.')
->screenshot('task_show_new_empty_title');
});
}
}
新規追加のページを表示した後、すぐに「登録」ボタンを押下している。 その後にコントローラーの時と同じメッセージが画面に表示されているか、確認していることになる。
では、テストを実行してみよう。
$ vendor/bin/phpunit
PHPUnit 6.5.8 by Sebastian Bergmann and contributors.
................F.. 19 / 19 (100%)
Time: 28.78 seconds, Memory: 22.00MB
There was 1 failure:
1) Tests\Browser\TaskNewTest::testShowNewEmptyTitle_failed
Did not see expected text [The title field is required.] within element [body].
Failed asserting that false is true.
/Users/[ユーザ名]/task-manager/task-manager/vendor/laravel/dusk/src/Concerns/MakesAssertions.php:348
/Users/[ユーザ名]/task-manager/task-manager/vendor/laravel/dusk/src/Concerns/MakesAssertions.php:319
/Users/[ユーザ名]/task-manager/task-manager/tests/Browser/TaskNewTest.php:38
/Users/[ユーザ名]/task-manager/task-manager/vendor/laravel/dusk/src/Concerns/ProvidesBrowser.php:67
/Users/[ユーザ名]/task-manager/task-manager/tests/Browser/TaskNewTest.php:40
FAILURES!
Tests: 19, Assertions: 54, Failures: 1.
テスト失敗だ。
では、エラーメッセージが表示されるように、ビューへコードを追加しよう。
resources/views/tasks/new.blade.php
を下記のようにする。
<!doctype html>
<html lang="{{ app()->getLocale() }}">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css"
integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
<script src="https://code.jquery.com/jquery-3.3.1.min.js" crossorigin="anonymous"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js"
integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa"
crossorigin="anonymous"></script>
<title>Tasks</title>
</head>
<body>
<div class="container">
<h2>New Task</h2>
<div class="row">
<div class="col-md-offset-2 col-md-8">
@if ($errors->any())
<div class="alert alert-danger">
<ul>
@foreach ($errors->all() as $error)
<li>{{ $error }}</li>
@endforeach
</ul>
</div>
@endif
{!! Form::open(['action' => ['TaskController@create'], 'method' => 'post']) !!}
<table class="table table-hover">
<thead>
<tr>
<td>タイトル</td>
</tr>
</thead>
<tbody>
<tr>
<td>{{ Form::text('title', '', ['id' => 'title', 'class' => 'form-control']) }}</td>
</tr>
</tbody>
</table>
{{ Form::submit('登録', ['class' => 'btn btn-primary']) }}
{!! Form::close() !!}
</div>
</div>
</div>
</body>
</html>
エラーがあった場合($errors
に値があった場合)、
<form>
の上にエラーをリストで表示するようにしている。
ではこれで、テストの再実行をしてみよう。
$ vendor/bin/phpunit
PHPUnit 6.5.8 by Sebastian Bergmann and contributors.
................... 19 / 19 (100%)
Time: 27.38 seconds, Memory: 22.00MB
OK (19 tests, 54 assertions)
成功だ。
これでバリデーションエラーが発生した場合の、 「コントローラー」としてのエラー処理、 「ビュー」としてのエラー処理、 どちらも実装ができた。
バリデーションの処理機構はフレームワークが提供しているので、 それ自体は動くであろうことは想像できるはずだ。
ただ、複数の条件が存在する場合に、それぞれの条件が意図通りに処理されるのか、 特に境界値になるところが正しく処理できるのか、 ここをきちんと確かめておくことで単純なミスによるバグというものを減らすことができるようになる。
こういったところは、できるだけ手を抜かないでおきたい。 一度テストを作ってしまえば、これからずっと(自分で意識せずに)確認し続けられてお得だ。
次は第4章の最後、新規登録画面への遷移を作ろう。