Laravel5.7 HTTPテストチュートリアル

[ディップ Advent Calendar](https://qiita.com/advent-calendar/2018/dip-dev)の17日目です。

## はじめに
はじめまして。
ディップ株式会社に2018年新卒で入社し、求人系サービスの開発や社内向けツールの開発を行なっている@taku-0728です。
今回は私が業務で使っているLaravelのHTTPテストについてチュートリアル風に書いてみます。
Qiitaへの投稿は初めてゆえ至らない点もあるかと思いますが、遠慮なくコメントでご指摘いただければと思います。

## やること
Laravelでテストコードを書きながらユーザの新規登録機能を作ります。
ただし今回、`php artisan make:auth`コマンドは使いません。

## 作るもの
作るものはほぼこちらの丸パクリです。単純に作るだけならこちらのほうがわかりやすいと思います。
新規登録以外についても丁寧に書かれています。
https://laraweb.net/tutorial/1872/

## 開発環境
- OS：macOS Mojave 10.14.1
- Laravel：5.7
- Mysql：8.0
- Docker：Docker 2.0.0.0-mac81

## 準備
事前にlaradockで適当なコンテナを立ち上げてlaravelプロジェクトを作成してあること
.envファイルに適切な設定を記述してデータベースに接続できること



## 実装
### とりあえずアクセスできるページを作ってみる

まずartisanコマンドでUserControllerを生成します

```
$ php artisan make:controller UserController
```

ルーティングを設定します

``` routes/web.php
Route::get('/signup', 'UserController@signUp');
```

コントローラを編集します
ここではviewファイルを返すだけの処理しかかきません。

``` app/Http/Controllers/UserController.php
/*
省略
*/
class UserController extends Controller
{
  public function signUp(){
    return view('user.signup');
  }
}
```

適当にviewファイルを作ります。

``` resources/views/user/signup.blade.php
<html>
<head>
    <meta charset="utf-8"/>
</head>
<body>
    <h1>認証画面のテスト</h1>
</body>
</html>
```

http://localhost/signup にアクセスしてみましょう。「**認証画面のテスト**」とでかでかと表示されるはずです。

### テストコードを書いてみる
アクセスできるページができたので、ここで実際にテストコードを書いていきます。
Laravelのテストには2種類あり、クラスやメソッドなどモジュール単位の動作を検証する**ユニットテスト**とWebページやAPI機能を検証する**フィーチャテスト**がサポートされています。
今回はフィーチャテストを中心に行なっていきます。
下記のコマンドを実行するとテストコードが生成されます。

```
$ php artisan make:test TopTest
```

テストコードが無事生成されました。

```/tests/Feature/Toptest.php
<?php

namespace Tests\Feature;

use Tests\TestCase;
use Illuminate\Foundation\Testing\WithFaker;
use Illuminate\Foundation\Testing\RefreshDatabase;

class TopTest extends TestCase
{
    /**
     * A basic test example.
     *
     * @return void
     */
    public function testExample()
    {
        $this->assertTrue(true);
    }
}
```

これを先ほど作成したページに対するテストコードに書き換えていきます。
まず最初のテストパターンとして、「自分で作ったページにアクセスできるか」をテストしてみます。
先ほどのテストコードを以下のように書き換えます。

```Toptest.php
<?php

namespace Tests\Feature;

use Tests\TestCase;
use Illuminate\Foundation\Testing\WithFaker;
use Illuminate\Foundation\Testing\RefreshDatabase;

class TopTest extends TestCase
{
    /**
     * トップページの表示内容のテスト
     *
     * @return void
     */
    public function testExample()
    {
      $response = $this->get('/signup');
      $response->assertStatus(200);
    }
}
```

上記のテストコードを実行してみましょう。

```
$ vendor/bin/phpunit tests/Feature/TopTest.php
PHPUnit 7.5.0 by Sebastian Bergmann and contributors.

.                                                                   1 / 1 (100%)

Time: 492 ms, Memory: 12.00MB

OK (1 test, 1 assertion)
```

無事テストが通り、アクセスしてステータスコード200が返ってきたことが確認できました。
次はviewファイルを拡張していきます。

``` resources/views/user/signup.blade.php
<html>
<head>
    <meta charset="utf-8"/>
    <title>チュートリアル</title>
    <link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet">
    <link href="https://cdnjs.cloudflare.com/ajax/libs/flat-ui/2.3.0/css/flat-ui.min.css" rel="stylesheet">
    <link href="starter-template.css" rel="stylesheet">
</head>
<body>
  <div class="container" style="max-width: 680px;">
    <div class="row">
      <form action="/profile" method="post" class="form-horizontal" style="margin-top: 50px;">
        <div class="form-group">
          <label class="col-sm-3 control-label" for="InputName">氏名</label>
          <div class="col-sm-9">
            <input type="text" name="name" class="form-control" id="InputName" placeholder="氏名">
          </div>
        </div>

        <div class="form-group">
          <label class="col-sm-3 control-label" for="InputEmail">メール・アドレス</label>
          <div class="col-sm-9">
            <input type="email" name="email" class="form-control" id="InputEmail" placeholder="メール・アドレス">
          </div>
        </div>

        <div class="form-group">
          <label class="col-sm-3 control-label" for="InputPassword">パスワード</label>
          <div class="col-sm-9">
            <input type="password" name="password" class="form-control" id="InputPassword" placeholder="パスワード">
          </div>
        </div>

        <div class="form-group">
        <label class="col-sm-3 control-label" for="area1">住所（エリア）</label>
          <div class="col-sm-9">
            <select name="area" data-toggle="select" class="form-control select select-default" id="area1">
              <option>北海道</option>
              <option>東北</option>
              <option>関東</option>
              <option>中部</option>
              <option>近畿</option>
              <option>中国</option>
              <option>四国</option>
              <option>九州</option>
              <option>沖縄</option>
            </select>
          </div>
        </div>

        <div class="form-group">
          <div class="col-sm-offset-3 col-sm-9">
            <button type="submit" class="btn btn-primary btn-block">新規登録</button>
          </div>
        </div>
        {{ csrf_field() }}
      </form>
    </div>
  </div>

</body>
</html>
```

入力用フォームの作成と、bootstrapを使って少し見た目を整えました。
入力用のフォームができたので登録処理に入っていきます。

### 登録準備

まずartisanコマンドでテーブル作成します。

```
php artisan make:migration create_users_table
```

生成されたマイグレーションファイルに以下のように追記します。

``` database/migrations/xxxx_xx_xx_xxxxxx_create_user_table.php
<?php

use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;

class CreateUsersTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
      Schema::create('users', function (Blueprint $table) {
        $table->increments('id');
        $table->timestamps();
        $table->string('email')->unique();
        $table->string('password');
        $table->string('name');
        $table->string('area');
        $table->rememberToken();
      });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
      Schema::dropIfExists('users');
    }
}
```

編集したマイグレーションファイルを実行します。

```
php artisan migrate
```

http://localhost:8080 にアクセスして、userテーブルが正しく生成されていれば成功です。

### 登録用テストコード生成
viewファイルに合わせてテストコードも編集していきます。
先ほど作成したテストコードを以下のように書き換えます。

``` Toptest.php
<?php

namespace Tests\Feature;

use Tests\TestCase;
use Illuminate\Foundation\Testing\WithFaker;
use Illuminate\Foundation\Testing\RefreshDatabase;

class TopTest extends TestCase
{
    /**
     * トップページの表示内容のテスト
     *
     * @return void
     */
    public function testTopVisit()
    {
      $response = $this->get('/signup');
      $response->assertStatus(200);
    }

    /**
     * 入力されたパラメータの送信テスト
     *
     * @return void
     */
     public function testPost()
     {
       $response = $this->from('/signup')->post('/profile', ['name' => 'テスト太郎', 'email' => 'test@test.com', 'password' => 'test', 'area' => '北海道']);
       $this->assertDatabaseHas('users', ['email' => 'test@test.com']);
       $response->assertStatus(200);

     }
}
```

新しく「入力されたパラメータの送信テスト」を追記しました。http://localhost/signup からhttp://localhost/profile にpostリクエストを送信し、DBのusersテーブルにemailカラムが```test@test.com```のレコードが存在するか確認した後、ステータスコード200が返ってくるかをテストしています。
当然まだページを作っていないので失敗しますが上記のテストコードを実行してみます。
ここで注意点が1点あります。
http://localhost/signup からリクエストを送信する必要があるので、dockerコンテナの中に入ってコマンドを実行してください。

```
$ docker-compose exec workspace bash
$ vendor/bin/phpunit tests/Feature/TopTest.php
PHPUnit 7.5.0 by Sebastian Bergmann and contributors.

.F                                                                  2 / 2 (100%)

Time: 2.24 seconds, Memory: 18.00MB

There was 1 failure:

1) Tests\Feature\TopTest::testPost
Failed asserting that a row in the table [users] matches the attributes {
    "email": "test@test.com"
}.

The table is empty.

/var/www/vendor/laravel/framework/src/Illuminate/Foundation/Testing/Concerns/InteractsWithDatabase.php:24
/var/www/tests/Feature/TopTest.php:31

FAILURES!
Tests: 2, Assertions: 2, Failures: 1.
```

現状ではusersテーブルは空なので、当然テストは失敗します。
このテストコードが成功するようにコードを追記していきます。

### 登録処理
まずルーティングを追記します。

``` routes/web.php
Route::post('/profile', 'UserController@profile');
```

ルーティングができたので、Controllerを編集していきます。

``` app/Http/Controllers/UserController.php
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use App\User;

class UserController extends Controller
{
  public function signUp() {
    return view('user.signup');
  }

  public function profile(Request $request) {

    // バリデーション
    $validationData = $request->validate([
      'name' => 'required',
      'email' => 'email|required|unique:users,email',
      'password' => 'required|min:4',
      'area' => 'required'
    ]);

    // DBインサート
    $user = new User([
      'name' => $validationData['name'],
      'email' => $validationData['email'],
      'password' => bcrypt($validationData['password']),
      'area' => $validationData['area']
    ]);

    // 保存
    $user->save();

    return view('user.profile');
  }

}
```

Modelも追記する必要があるので追記します。すでにapp/User.phpが存在するのでこちらを以下のように編集します。

``` app/User.php
<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class User extends Model
{
  protected $fillable = ['name', 'email', 'password', 'area'];
}
```
また、登録確認用のviewファイルを雑に作成します。

``` resources/views/user/profile.blade.php
<div style="margin-top: 30px; text-align: center;">
  <h3>新規登録完了しました。</h3>
</div>
```

Controller, Model, Viewができたので、先ほどのテストコードを再度実行してみます。

```
$ vendor/bin/phpunit tests/Feature/TopTest.php
PHPUnit 7.5.0 by Sebastian Bergmann and contributors.

..                                                                  2 / 2 (100%)

Time: 1.91 seconds, Memory: 14.00MB

OK (2 tests, 3 assertions)
```

無事テストが通ったことが確認できました。 http://localhost:8080 にアクセスすると、usersテーブルにも値が入っていることが確認できるはずです。
http://localhost/signup で実際にフォームに値を入力して新規登録ボタンを押すと、登録完了したことが確認できるはずです。
これで新規登録機能が完成しました！

## まとめ
LaravelのHTTPテストについて、新規登録機能を例に書いてみました。
しかし、今回テストとしては

1. TOPページにアクセスできること
2. postした任意の値をDBに正常に登録できること

の2点しか確認できていません。実際にテストコードを書く場合、もっと様々なテストパターンについてテストコードを書く必要がありますが、その際に本記事が参考になればと思います。

## 最後に
最後まで読んでくださった方ありがとうございました。
冒頭にも言いましたが、Qiitaへの投稿は初めてゆえ至らない点も多くあるかと思いますが、「ここはこうしたほうがいい」や、「ここが気になった」などあれば遠慮なくコメントにて教えていただければと思います。
また機会があれば新規登録機能以外についても書いてみようと思います。
本当にありがとうございました！

## 参考
[認証画面の自作 ～新規登録～](https://laraweb.net/tutorial/1872/)
[【書籍】PHPフレームワーク Laravel Webアプリケーション開発](https://www.amazon.co.jp/dp/4802611846/ref=asc_df_48026118462550717/?tag=jpgo-22&creative=9303&creativeASIN=4802611846&linkCode=df0&hvadid=295706574430&hvpos=1o1&hvnetw=g&hvrand=1773438745252705651&hvpone=&hvptwo=&hvqmt=&hvdev=c&hvdvcmdl=&hvlocint=&hvlocphy=1028853&hvtargid=pla-523780196376&th=1&psc=1)
[HTTPテスト 5.7 Laravel](https://readouble.com/laravel/5.7/ja/http-tests.html)
[Laravelでテストコードを書くためのチュートリアル](https://qiita.com/niisan-tokyo/items/264d4e8584ed58536bf4)