Laravel8でHTTPテスト

## はじめに
初めまして。ヤマウチです。
今回は私が業務で実装しているLaravelのHTTPテストに関してPHPUnitのセットアップからテスト実行までの手順をチュートリアル風に書いてみました。
技術面がまだまだ乏しい弱々エンジニアですが、一人でも多くの方に参考になると嬉しいです。
誤った記載がありましたらコメントにてご指摘くださいm(__)m

## やること
今回はHTTPテストを実施することを目的としますので、すでに実装済みのコードがあることを前提に

- テストの準備
- データの作成
- 使用するトレイトやメソッド

を中心に紹介していきます。
マイグレーションや機能の実装などの紹介は行いません。
また、今回はコントローラの機能テスト(HTTPテスト)を行うていで進めていきます。


## テストの準備
Laravelでは、ユニットテストも考慮して作られており、最初からPHPUnitが標準で備わっています。
以下のコマンドを打つことでテスト用のファイルが`tests/Feature`ディレクトリへ配置されます。

```
$ php artisan make:test UserTest
Test created successfully.
```

また、`tests/Unit`ディレクトリ内にテストを作成したい場合は、make:testコマンドを実行するときに--unitオプションを使用することで作成することが可能です。

## .env.testingの設定に関して
テストを行う際、開発用のDBを汚さずにテストを行いたいこともあるかと思います。そういった場合に`.env.testing`を別で作成しておくことが必要があります。
バージョンは異なりますが、こちらの記事が参考になったので紹介します。[【Laravel】.env.testingの使用方法と注意点](https://qiita.com/akkino_D-En/items/024661836b7570d78704)

## テストを実行してみる
先程のコマンドでテストファイルを作成すると、`tests/Feature`ディレクトリに以下の内容のテストが作成されます。

```php
<?php

namespace Tests\Feature;

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

class UserTes extends TestCase
{
    /**
     * A basic feature test example.
     *
     * @return void
     */
    public function testExample()
    {
        $response = $this->get('/');

        $response->assertStatus(200);
    }
}
```
内容としては、「get('/')にアクセスすると、レスポンスステータスコード200が返されること。」が書かれています。
早速こちらを試してみます。
基本的には`$ ./vendor/bin/phpunit`コマンドで全てのテストを実行することができますが、ディレクトリやファイルを指定してテストを実行することも可能です。

```
$ ./vendor/bin/phpunit ./tests/Feature/
PHPUnit 8.5.8 by Sebastian Bergmann and contributors.

.....                                                               5 / 5 (100%)

Time: 1.87 seconds, Memory: 26.00 MB

OK (5 tests, 8 assertions) 
```
上記のように出力されれば無事テストが通ったことになり、イメージ通りのプログラムが書けていることの証明になります。（ちゃんとしたテストが書けていれば）
しかし、上記のような出力だと、どのテストを実施したかいまいち分かり難いかと思います。
そこで、PHPUnitにはテストをより分かりやすくするためのオプションも備えていますので、ここでいくつか紹介します。

### --list-testsオプション
どのファイルのどのテストが実行されたかを表示してくれます。

```
$ ./vendor/bin/phpunit --list-tests ./tests/Feature
PHPUnit 8.5.8 by Sebastian Bergmann and contributors.

Available test(s):
 - Tests\Feature\Controllers\AuthTest::test_ユーザーが作成されること
 - Tests\Feature\Controllers\AuthTest::test_ユーザーが登録されること
 - Tests\Feature\Controllers\PostTest::test_投稿のテスト
 - Tests\Feature\ExampleTest::testBasicTest
 - Tests\Feature\UserTest::testExample
```
### --testdoxオプション
テストを実行しながら、どのテストが成功し、あるいは失敗したかをリアルタイムで確認することができます。
ついつい--testboxにしがちですが、**--testdox**なのでお間違いなく。

```
$ ./vendor/bin/phpunit --testdox ./tests/Feature
PHPUnit 8.5.8 by Sebastian Bergmann and contributors.

Auth (Tests\Feature\Controllers\Auth)
 ✔ ユーザーが作成されること
 ✔ ユーザーが登録されること

Post (Tests\Feature\Controllers\Post)
 ✔ 投稿のテスト

Example (Tests\Feature\Example)
 ✔ Basic test

User (Tests\Feature\User)
 ✔ Example

Time: 1.8 seconds, Memory: 26.00 MB

OK (5 tests, 8 assertions)
```
### --filterオプション
実行したいテストを、パターンで指定することが可能。
以下の例では、**Example**から始まるファイルのみのテストを実行しています。

```
./vendor/bin/phpunit --filter Example --testdox ./tests/Feature
PHPUnit 8.5.8 by Sebastian Bergmann and contributors.

Example (Tests\Feature\Example)
 ✔ Basic test

User (Tests\Feature\User)
 ✔ Example

Time: 1.46 seconds, Memory: 24.00 MB

OK (2 tests, 2 assertions)
```
また、`--filter`オプションでは、テストファイルだけではなく、特定のテストを指定することも可能です。

### その他オプション
PHPUnitのコマンドラインオプションに関しては`phpunit --help`コマンドにて確認することができます。
[コマンドラインオプション](https://phpunit.readthedocs.io/ja/latest/textui.html)

## コントローラのテスト
それでは本題のコントローラのテストを行います。
ですがその前に準備として、テストデータを作成する必要があるので、作成方法を記載します。

### モデルファクトリの定義
テストを行う際、データベースにあらかじめいくつかのレコードを準備しておく必要があります。このテストデータを作成するときに各カラムの値を自分でいちいち指定する代わりに、Laravelではモデルファクトリを使用し、各Eloquentモデルのデフォルト属性を定義できます。
[モデルファクトリの定義](https://readouble.com/laravel/8.x/ja/database-testing.html)

### モデルファクトリの作成
モデルファクトリを作成するにはまず、以下のコマンドを実行します。

```
php artisan make:factory UserFactory --model=User
```
`--model`オプションは、ファクトリにより作成するモデルの名前を指定するために使用します。
上記コマンドを実行すると、`database/factories`ディレクトリに以下のファイルが作成されます。
※return以下は、必要なレコードを作成するために各々で定義します。
**UserFactory.php**

```php:database/factories/UserFactory.php
<?php

namespace Database\Factories;

use App\Models\User;
use Illuminate\Database\Eloquent\Factories\Factory;
use Illuminate\Support\Str;
use Illuminate\Support\Facades\Hash;

class UserFactory extends Factory
{
    /**
     * The name of the factory's corresponding model.
     *
     * @var string
     */
    protected $model = User::class; // モデルとの紐付け

    /**
     * Define the model's default state.
     *
     * @return array
     */
    public function definition()
    {
        return [　                                        // 以下は適宜定義
            'name' => $this->faker->name,
            'email' => $this->faker->unique()->safeEmail,
            'email_verified_at' => now(),
            'password' => Hash::make(12345678), 
            'remember_token' => Str::random(10),
        ];
    }
}
```
**User.php**

```php:app/Models/User.php
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;

class User extends Authenticatable
{
    use HasFactory; // 必要
}
```
以上でデータ作成のための前準備は完了です。
あとは必要な時にモデルからfactory()メソッドを呼び出すことでデータを作成することができます。
※Laravel8から、モデルファクトリを使用したデータの作成方法がこれまでと異なります。
以下の記事が非常に参考になります。
[Laravel8で完成されたModelFactoryの使い方](https://qiita.com/ucan-lab/items/788d1a603f2b53ebb999)

### テスト作成（GETリクエスト）
ここからは実際にコントローラのテストを書いていきます。
すでにログイン済みの画面から、別の画面へ遷移できることをテストで確認していきたいと思います。

```php:tests/Feature/Controllers/ControllerTest.php
<?php

namespace Tests\Feature\Controllers;

use Tests\TestCase;
use App\Models\User;
use Database\Seeders\GenreSeeder;

class ControllerTest extends TestCase
{
    use WithoutMiddleware;

    public function test_画面遷移のテスト()
    {
        // 認証済みユーザーの作成
        $loginUser = User::factory()->create(); // Userモデルからfactory()を呼び出すことでユーザーを作成
        $request = $this->actingAs($loginUser)
        ->get('main/display');
        $request->assertOk();
    }
}

// ------------------------------------------------------
Controller (Tests\Feature\Controllers\Controller)
 ✔ 画面遷移のテスト

Time: 1.9 seconds, Memory: 28.00 MB

OK (1 tests, 1 assertions)

```
上記テストのポイントとして気をつけなければいけないことは、「すでにログイン済みの画面から、別の画面へ遷移」することです。
つまり、認証済みのユーザーからリクエストを投げなければなりません。
そこで必要になるのが、`actingAs()`メソッドです。[公式ドキュメント](https://readouble.com/laravel/8.x/ja/http-tests.html)に詳しい説明があるのでぜひ読んでみてください。
もし、actingAs()を忘れていた場合、ステータスコード302が返されるので、そこも同時に覚えておくと良いでしょう。
[HTTP 302](https://ja.wikipedia.org/wiki/HTTP_302)

```
Post (Tests\Feature\Controllers\ControllerTest)
 ✘ 画面遷移のテスト
   ┐
   ├ Response status code [302] does not match expected 200 status code.
   ├ Failed asserting that false is true.
```
### テスト作成（POSTリクエスト）
次は、POSTリクエストのテストを書いていきます。
よくある投稿機能のテストを想定とし、

- タイトル
- ジャンル
- 本文

をリクエストパラメータとします。

```php:tests/Feature/Controllers/PostTest.php
<?php

namespace Tests\Feature\Controllers;

use Illuminate\Foundation\Testing\WithoutMiddleware;
use Tests\TestCase;
use App\Models\User;
use App\Models\Post;
use Database\Seeders\GenreSeeder;

class PostTest extends TestCase
{
    use WithoutMiddleware; // ミドルウェアを無効化

    public function test_投稿のテスト()
    {
        $this->withoutMiddleware([VerifyCsrfToken::class]); // 特定のミドルウェアを無効化
        
        $loginUser = User::factory()->create();
        $request = $this->actingAs($loginUser)
        ->post('/post/insert', [
            'title' => 'テスト',
            'comment' => 'これはテストです。',
            'genres' => [3, 4, 5]
        ]);
        $newPost = Post::where('title', 'テスト')->first();
        self::assertSame('これはテストです。', $newPost->comment);
        $request->assertRedirect('main/display');
    }
}

// --------------------------------------------------------

Post (Tests\Feature\Controllers\Post)
 ✔ 投稿のテスト
 
Time: 1.9 seconds, Memory: 28.00 MB

OK (1 tests, 2 assertions)

```
ここでもいくつかポイントを紹介します。
`$this->withoutMiddleware([VerifyCsrfToken::class]);`
ここではテストの際に無効化したいミドルウェアを記載しています。
今回の例で説明しますと、POSTリクエストの際に必要となる**CSRF(クロス・サイト・リクエスト・フォージェリ)**を無効化しています。
`use WithoutMiddleware;`ここの記載のみでもミドルウェアを無効化することができますが、`$this->withoutMiddleware();`によって、特定のミドルウェアに絞らないと全てのミドルウェアを無効化してしまうことになるので、必要な時に必要なミドルウェアのみ無効化しましょう。

[Laravel 8.x ミドルウェア](https://readouble.com/laravel/8.x/ja/middleware.html)
[参考にした記事](https://qiita.com/NakanishiTetsuhiro/items/a4507cb147355b3d8895)

また、上記の記載がなければステータスコード419（Laravelの非公式HTTPコード）が返されますので、同時に覚えておくと良いでしょう。
[ステータスコード419](https://markohoven.com/2018/01/23/419-http-status-code/)

```
✘ 投稿のテスト
   ┐
   ├ Response status code [419] is not a redirect status code.
   ├ Failed asserting that false is true.   
```

## まとめ
今回はPHPUnitセットアップ → データ作成 → テスト実施と、かなり大雑把な説明となってしまいました。
また、本記事では、2つのHTTPテストしか実施していませんが、実際にテストを書く際にはもっと様々なテストパターンを作成しなければなりませんし、PHPUnitには他にもたくさんの便利な機能が備わっています。
次回以降は、さらに焦点を絞った内容でテストに関する記事を書いていけたらいいなと考えています。