【Laravel】テストを書くときによく使うコード集

Laravel
スポンサーリンク

はじめに

この記事は Laravel #2 Advent Calendar 2018 の14日目の記事です。

内容

今回はLaravelでテストコードを書く際に、普段使っている処理をまとめてみました。

LaravelにはHTTPリクエストのテストとして、Featureテストがあります。

Laravel - The PHP Framework For Web Artisans
Laravel is a PHP web application framework with expressive, elegant syntax. We’ve already laid the foundation — freeing you to create without sweating the small...

自分はFeatureテストから書き始めることが多いため、この記事の内容がFeatureテスト関連寄りになっているかもしれませんが、テスト周りで何か思いついたら更新していきたいと思います。

“route()”の使用など、テストだけに限らないないところもありますが、Laravelを触り始めたばかりの方や、Laravelでまだテストコードを書いたことのない方の参考になれば幸いです。

エントリーポイントにリクエストを送信する

GETリクエストを送信する

$response = $this->get('/api/task');

POSTリクエストを送信する

$response = $this->post('/api/task', [
    'body' => 'foobar'
]);

PUTリクエストを送信する

$response = $this->put('/api/task/' . $id, [
    'body' => 'foobar'
]);

PATCHリクエストを送信する

$response = $this->patch('/api/task/' . $id, [
    'body' => 'foobar'
]);

エントリーポイントにnameを設定する

Laravelのルーティングでは、各エントリーポイントにnameを設定できます。

nameを設定すると、“route()”メソッドを使ってURIを取得できるため、
後にエントリーポイントのURIが変更された場合でも、各処理を変更せずに済ませられます。

ルーティングにnameが設定されていない場合

Route::get('/task/{id}', 'TaskController@show');

nameを設定していない場合、リクエストを送信するテストは次のようになります。

$response = $this->get("/api/task/1");

この場合URLが変更されると、”/api/task”を設定している箇所を変更する必要が出てきます。

ルーティングにnameを設定した場合

Route::get('/task/{id}', 'TaskController@show')
 ->name('api.task.show');

nameを設定した場合のテストは、次のように書けます。
URLが変更されたとしても、nameを変更する必要がなければ既存のコードに影響を与えずに済みます。

$response = $this->get(route('api.task.show', ['id' => 1]));

“route()”メソッドはテストクラスだけでなく、Bladeテンプレートやその他のクラスからでも呼び出し可能なメソッドですので、ルーティングを参照する処理では頻繁に利用しています。

JSONが一致するかどうかを比較する

レスポンスがJSONで返されるときに使用するのが、”assertJsonStringEqualsJsonString()”です。

次の例では、”$response->content()”を使用してレスポンスのJSONを取得しています。
“json_encoe()”に期待する結果の配列を渡すして、JSONを比較することができます。

$response = $this->get(route('api.task.show', ['id' => $task->id]));

$this->assertJsonStringEqualsJsonString(
    json_encode([
        'id'   => 1,
        'body' => 'foobar',
    ]),
    $response->content()
);

DIする(依存性を解決する)クラスを差し替える

外部のAPIなどに依存する処理をクラスにまとめていて、コンストラクターインジェクションや”app()”メソッドを使用して依存性を解決(DI)し、インスタンスを取得しているとします。
テストのときにはそういったクラスの処理を実行させたくない場合、”instance()”メソッドを使ってDIするクラスを差し替えることができます。

例えばMockeryを使ってモックを作成し、DIするクラスを差し替えるといったことが可能になります。

public function test_DIするクラスの差し替え() {
  // モックするクラスのインスタンスを取得
  $mock = app(MockSample::class);  

  // Sampleクラスの依存性を解決するとき、$mockが使用されます
  $this->instance(Sample::class, $mock);

  // Do something
}

テスト失敗時のメッセージをわかりやすくする

リクエストを送信するテスト等では、テスト失敗時のメッセージが分かりづらいことがあります。
例えば下記のようなメッセージが表示されます。

public function test_ステータスコード {
  $response = $this->post(route('api.task.store'), [
    'body' => 'foobar'  
  ]);  

  $response->assertStatus(200);
}
Expected status code 200 but received 403.
Failed asserting that false is true.

そんなときはテスト内で”withoutExceptionHandling()”を使用すると、メッセージがわかりやすくなることがあります。

public function test_ステータスコード {

  $this->withoutExceptionHandling();

  $response = $this->post(route('api.task.store'), [
    'body' => 'foobar'  
  ]);  

  $response->assertStatus(200);
}

“withoutExceptionHandling()”以外は元のテストコードと同じですが、次のメッセージが表示されるようになりました。

Illuminate\Auth\Access\AuthorizationException : This action is unauthorized.

ちなみにAuthに関わっていそうな処理を調べたところ、上記のエラーはFormRequestの”authorize()”がfalseになっていったことが原因でした。

ファクトリーを使ってデータを生成する

ファクトリーを使用すると、ダミーデータを簡単に生成することができます。

次に示す例は、Taskモデルのファクトリーです。

<?php

use Faker\Generator as Faker;

$factory->define(\App\Models\Task::class, function (Faker $faker) {
    return [
        'user_id' => function () {
            return factory(\App\User::class)->create()->id;
        },
        'body' => $faker->text,
        'complete_flag' => false,
    ];
});

次のように”create()”に連想配列を指定すると、引数で渡したパラメータがセットされたモデルをDBに保存した上で、インスタンスを返却します。

DBにデータを保存せず、インスタンスだけを生成したい場合は、”create()”ではなく”make()”が使用できます。

$task = factory(Task::class)->create([
    'user_id' => $user->id,
    'body' => 'foobar',
]);

ファクトリーファイルを1から生成するのが面倒な場合は、拙作のライブラリがありますのでよければ試してみて下さい。

【Laravel】テーブル定義からファクトリーファイルを自動生成するライブラリを作りました
Laravelでは、ファクトリーファイルを使って、テストデータを簡単に準備することができます。そんなファクトリーファイルですが、難点はファクトリーファイルに設定するカラム名を自分で定義しなければならないことです。ファクトリーファイル...

テストを実行したときに生成されるデータを残したくない場合は、“DatabaseMigrations”、”DatabaseTransactions”、”RefreshDatabase”といったトレイトが用意されているので、それぞれの違いを確認の上で利用すると便利です。

テスト時間を特定の日時に設定する

テストの実行日時を未来や過去に指定したい場合は、Carbonの”setTestNow()”を使用します。

“setTestNow()”を使うと、そのテストを実行している間、Carbonは設定した日時を現在日時として扱うようになります。

$now = Carbon::createFromFormat('Y-m-d H:i:s', '2018-12-14 11:22:33');  
Carbon::setTestNow($now);