Laravelでunitテストを書きたいけど、ちょっと難しそうだからどうやって書けばいいのかわからない…
この記事では上記の悩みを解決します。
Laravelでunitテスト書くぞ!って思ってもなかなか勉強するハードルが高くて大変ですよね。
この記事では、Laravelのunitテストでバリデーションや基本的な登録、更新、削除処理のテストコードの書き方について解説します。
ぜひ参考にしてみてください。
Laravelのバージョン:11.7.0
LaravelのUnitテストについてざっくりと解説
Laravelのunitテストについて、コマンドやよく使うメソッドなどをざっくりと解説し、その後に実際の処理を見ていきながらunitテストでについて解説していきます。
まずは、unitテストのファイルを作成するときのコマンド
php artisan make:test ファイル名
tests/Feature/にテストファイルが作成されます。
通例では、ファイル名は⚪︎⚪︎Testとつけるので、例えばPostCreateTestのようにファイル名を作成するのがいいでしょう。
全てのテストを実行する場合は、以下
php artisan test
指定したファイルだけテストを実行したい場合は、以下
php artisan test ファイルの相対パス
例えば、tests/Feature/Plan/PlanCreateTest.phpにあるテストファイルを実行したい場合は、以下のように実行します。
php artisan test tests/Feature/Plan/PlanCreateTest.php
ここからはよく出てくるメソッドの解説です。
assertEquals(引数1, 引数2)
引数1と引数2が等しいかチェックします。等しくない場合は、エラーを返します。
assertStatus(A)
ステータスコードがAになるかチェックします。例えば、正常系のレスポンスは200レスポンスが返ってきますが、assertStatus(200)でチェックできます。
assertDatabaseHas('テーブル名', 条件)
指定したテーブル名に指定した条件のレコードが存在するかチェックします。登録処理や更新処理を行った際に実際にデータベースにレコードが登録または更新されるかチェックする時に使用します。また、削除処理は、以下のようなメソッドでもテストできます。
// 指定したデータが、テーブルに含まれないことをチェック
$this->assertDatabaseMissing($table, array $data);
// 指定したレコードが削除されていることをアサート
$this->assertDeleted($table, array $data);
// 指定したレコードがソフトデリートされていることをチェック
$this->assertSoftDeleted($table, array $data);
Laravelでは、ログインしてから登録処理を実行するようにすることもあるかと思います。
ログインをシミュレートしてテストを実行する時には、actingAsを使います。
例えば、ログイン後に一覧画面を表示するみたいなテストをしたい場合は、以下
$user = factory(User::class)->create();
$response = $this->actingAs($user)
->get('/');
$this->actingAs($user)でログイン認証済みのユーザーということを表しています。
Unitテスト①バリデーションのテスト
LaravelのUnitテストでバリデーションテストを書いていきます。
フォームリクエストの内容は以下になります。必須、最大文字、数値などを基本的なものになります。
app/Http/Requests/PlanRequest.php
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class PlanRequest extends FormRequest
{
/**
* Get the validation rules that apply to the request.
*
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
*/
public function rules(): array
{
return [
'tag_id' => 'required|integer',
'plan_title' => 'required|string|max:50',
'plan_explanation' => 'required|string',
'plan_status' => 'required|integer',
'amount' => 'required|integer',
];
}
}
テストファイルを作成していきましょう!
テストファイルの作成は、以下のコマンドでできます。
$ php artisan make:test Request/PlanCreateTest --unit
tests/Unit/Request/PlanRequestTest.phpが作成されます。
※今回は/Unit/Request/にテストファイルを作成したかったので、上記のようにしましたが、tests/Unit/にファイルを作成したい場合は、php artisan make:test PlanCreateTest –unitで作成できます。
tests/Unit/Request/PlanRequestTest.php
<?php
namespace Tests\Unit\Request;
use Tests\TestCase;
use Illuminate\Support\Facades\Validator;
use PHPUnit\Framework\Attributes\Test;
use PHPUnit\Framework\Attributes\DataProvider;
use App\Http\Requests\PlanRequest;
class PlanRequestTest extends TestCase
{
/**
* それぞれのテストが実行される前に行う処理
*/
public function setUp(): void
{
parent::setUp();
$request = new PlanRequest();
$this->rules = $request->rules();
}
/**
* プラン登録バリデーションテスト
* OK
* 正常パターン
*/
public function test_plan_request_ok()
{
$data = [
'expected' => true,
'tag_id' => 1,
'plan_title' => 'タイトル',
'plan_explanation' => 'タイトル説明',
'plan_status' => 1,
'amount' => 100,
];
$validator = validator($data, $this->rules);
$this->assertEquals($data['expected'], $validator->passes());
}
/**
* プラン登録バリデーションテスト
* NG
* tag_idがnull
*/
public function test_plan_request_tag_id_null()
{
$data = [
'expected' => false,
'tag_id' => null,
'plan_title' => 'タイトル',
'plan_explanation' => 'タイトル説明',
'plan_status' => 1,
'amount' => 100,
];
$validator = validator($data, $this->rules);
$this->assertEquals($data['expected'], $validator->passes());
}
/**
* プラン登録バリデーションテスト
* NG
* tag_idが数字以外
*/
public function test_plan_request_tag_id_not_integer()
{
$data = [
'expected' => false,
'tag_id' => 'あ',
'plan_title' => 'タイトル',
'plan_explanation' => 'タイトル説明',
'plan_status' => 1,
'amount' => 100,
];
$validator = validator($data, $this->rules);
$this->assertEquals($data['expected'], $validator->passes());
}
/**
* プラン登録バリデーションテスト
* NG
* plan_titleがnull
*/
public function test_plan_request_plan_title_null()
{
$data = [
'expected' => false,
'tag_id' => 1,
'plan_title' => null,
'plan_explanation' => 'タイトル説明',
'plan_status' => 1,
'amount' => 100,
];
$validator = validator($data, $this->rules);
$this->assertEquals($data['expected'], $validator->passes());
}
/**
* プラン登録バリデーションテスト
* NG
* plan_titleが文字列以外
*/
public function test_plan_request_plan_title_not_string()
{
$data = [
'expected' => false,
'tag_id' => 1,
'plan_title' => 1,
'plan_explanation' => 'タイトル説明',
'plan_status' => 1,
'amount' => 100,
];
$validator = validator($data, $this->rules);
$this->assertEquals($data['expected'], $validator->passes());
}
/**
* プラン登録バリデーションテスト
* NG
* plan_titleが50文字以上
*/
public function test_plan_request_plan_title_not_max_50()
{
$data = [
'expected' => false,
'tag_id' => 1,
'plan_title' => 'プランタイトルです。プランタイトルです。プランタイトルです。プランタイトルです。プランタイトルです。プ',
'plan_explanation' => 'タイトル説明',
'plan_status' => 1,
'amount' => 100,
];
$validator = validator($data, $this->rules);
$this->assertEquals($data['expected'], $validator->passes());
}
/**
* プラン登録バリデーションテスト
* NG
* plan_explanationがnull
*/
public function test_plan_request_plan_explanation_null()
{
$data = [
'expected' => false,
'tag_id' => 1,
'plan_title' => 'プランタイトル',
'plan_explanation' => null,
'plan_status' => 1,
'amount' => 100,
];
$validator = validator($data, $this->rules);
$this->assertEquals($data['expected'], $validator->passes());
}
/**
* プラン登録バリデーションテスト
* NG
* plan_explanationが文字列以外
*/
public function test_plan_request_plan_explanation_not_string()
{
$data = [
'expected' => false,
'tag_id' => 1,
'plan_title' => 'プランタイトルです。プランタイトルです。プランタイトルです。プランタイトルです。プランタイトルです。プ',
'plan_explanation' => 1,
'plan_status' => 1,
'amount' => 100,
];
$validator = validator($data, $this->rules);
$this->assertEquals($data['expected'], $validator->passes());
}
/**
* プラン登録バリデーションテスト
* NG
* plan_statusがnull
*/
public function test_plan_request_plan_status_null()
{
$data = [
'expected' => false,
'tag_id' => 1,
'plan_title' => 'プランタイトルです。プランタイトルです。プランタイトルです。プランタイトルです。プランタイトルです。プ',
'plan_explanation' => 'タイトル説明',
'plan_status' => null,
'amount' => 100,
];
$validator = validator($data, $this->rules);
$this->assertEquals($data['expected'], $validator->passes());
}
/**
* プラン登録バリデーションテスト
* NG
* plan_statusが数字以外
*/
public function test_plan_request_plan_status_not_integer()
{
$data = [
'expected' => false,
'tag_id' => 1,
'plan_title' => 'プランタイトルです。プランタイトルです。プランタイトルです。プランタイトルです。プランタイトルです。プ',
'plan_explanation' => 'タイトル説明',
'plan_status' => 'あ',
'amount' => 100,
];
$validator = validator($data, $this->rules);
$this->assertEquals($data['expected'], $validator->passes());
}
/**
* プラン登録バリデーションテスト
* NG
* amountがnull
*/
public function test_plan_request_amount_null()
{
$data = [
'expected' => false,
'tag_id' => 1,
'plan_title' => 'プランタイトルです。プランタイトルです。プランタイトルです。プランタイトルです。プランタイトルです。プ',
'plan_explanation' => 'タイトル説明',
'plan_status' => null,
'amount' => null,
];
$validator = validator($data, $this->rules);
$this->assertEquals($data['expected'], $validator->passes());
}
/**
* プラン登録バリデーションテスト
* NG
* amountが数字以外
*/
public function test_plan_request_amount_not_integer()
{
$data = [
'expected' => false,
'tag_id' => 1,
'plan_title' => 'プランタイトルです。プランタイトルです。プランタイトルです。プランタイトルです。プランタイトルです。プ',
'plan_explanation' => 'タイトル説明',
'plan_status' => 1,
'amount' => 'あ',
];
$validator = validator($data, $this->rules);
$this->assertEquals($data['expected'], $validator->passes());
}
}
setUp()で記載した内容は、各テストメソッドが実行される前に事前に実行されるものになります。
ここでPlanRequest.phpのバリデーションルールを取得しています。
‘expected’ => trueは正常パターン、’expected’ => falseはバリデーションエラーパターンです。
assertEqualsは、assertEquals(A, B)→AとBが一致するかどうかテストしています。
正常パターンの場合は、$this->assertEquals($data[‘expected’] = true, $validator->passes() = true);のように実際に値が来ていて、テストOKになる仕組みです。
全てのテストを実行する場合
$ php artisan test
作成したPlanRequestTest.phpだけ実行したい場合
$ php artisan test tests/Unit/Request/PlanRequestTest.php
テストが通過すればOKです。
Unitテスト②一覧表示のテスト
app/Http/Controllers/PlanController.php
/**
* プラン一覧取得
*/
public function get()
{
$plan = Plan::all()
$tags = Tag::all();
return view('plan', compact('plan', 'tags'));
}
tests/Feature/Plan/PlanGetTest.php
<?php
namespace Tests\Feature\Plan;
use App\Models\User;
use App\Models\Tag;
use App\Models\Plan;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Foundation\Testing\WithFaker;
use Tests\TestCase;
class PlanGetTest extends TestCase
{
use RefreshDatabase;
/**
* それぞれのメソッドが実行される前に行う処理
*/
public function setUp(): void
{
parent::setUp();
$this->user = User::factory()->create();
$this->tag = Tag::factory()->create();
$this->plan = Plan::factory()->create();
}
/**
* プラン一覧画面の表示テスト
*/
public function test_view_plan_get()
{
// actingAsでユーザー認証を実現し、プラン一覧画面を表示する
$response = $this->actingAs($this->user)->get('/plan');
// コントローラーからviewに変数が渡っているかのテスト
$response->assertViewHas('plan');
// データベースと一致する値があるかテスト
$data = Plan::find($this->plan->plan_id);
// 第1引数: チェック値, 第2引数: DBの値
$this->assertEquals($this->plan->plan_id, $data->plan_id);
// 画面の表示に成功しているかのテスト
$response->assertStatus(200);
}
}
setUp()については後続に記載している登録処理や更新処理をご確認ください。
assertViewHasでviewに変数が渡っているかテストできます。
Unitテスト③登録処理のテスト
app/Http/Controllers/PlanController.php
/**
* プラン登録
*/
public function store(PlanRequest $request) {
$user_id = Auth::user()->user_id;
Plan::create([
'user_id' => $user_id,
'tag_id' => $request->tag_id,
'plan_title' => $request->plan_title,
'plan_explanation' => $request->plan_explanation,
'plan_status' => $request->plan_status,
'amount' => $request->amount,
]);
session()->flash('planCreateSuccess','プランの登録が完了しました。');
return Redirect::to('plan');
}
Auth::user()は、Laravelのユーザー認証です。Plan::createでDBにデータ登録しています。
テストファイルを作成していきましょう!
$ php artisan make:test Plan/PlanTest.php
tests/Feature/にファイルを作成する場合は、以下でできます。
$ php artisan make:test PlanTest.php
※php artisan make:test PlanTest.php –unitのように、–unitをつければtests/Unit/に作成されますが、tests/Feature/にファイルを作成したい場合は特にオプションコマンドをつけてなくてもいいです。
tests/Feature/Plan/PlanCreateTest.php
<?php
namespace Tests\Feature\Plan;
use App\Models\User;
use App\Models\Tag;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Foundation\Testing\WithFaker;
use Tests\TestCase;
class PlanCreateTest extends TestCase
{
use RefreshDatabase;
/**
* プラン登録画面接続テスト
*/
public function test_view_plan_create(): void
{
$user = User::factory()->create();
$response = $this->actingAs($user)->get('/plan/create');
$response->assertStatus(200);
}
/**
* プラン登録処理テスト
*/
public function test_create_plan(): void
{
$user = User::factory()->create();
$tag = Tag::factory()->create();
$plan = [
'user_id' => $user->user_id,
'tag_id' => $tag->tag_id,
'plan_title' => 'Laravelを教えます',
'plan_explanation' => 'Laravelを全10回で教えます',
'plan_status' => 1,
'amount' => 10000,
];
$response = $this->actingAs($user)->post('/plan/store', $plan);
$response->assertStatus(302)->assertRedirect('/plan');
}
/**
* プラン登録処理後のDB保存確認テスト
*/
public function test_is_plan_data_store()
{
$user = User::factory()->create();
$tag = Tag::factory()->create();
$plan = [
'user_id' => $user->user_id,
'tag_id' => $tag->tag_id,
'plan_title' => 'Laravelを教えます',
'plan_explanation' => 'Laravelを全10回で教えます',
'plan_status' => 1,
'amount' => 10000,
];
$response = $this->actingAs($user)->post('/plan/store', $plan);
$this->assertDatabaseHas('plan', [
'user_id' => $user->user_id,
'tag_id' => $tag->tag_id,
'plan_title' => 'Laravelを教えます',
'plan_explanation' => 'Laravelを全10回で教えます',
'plan_status' => 1,
'amount' => 10000,
]);
}
}
actingAsでログインをテストする際に使います。ログイン状態をシュミレートしてくれるもので、例えばログインした後に何かを登録したりすると思いますが、actingAsを記載することでテストコード上でログインを実現できます。
登録処理が成功した際に、リダイレクトする際はassertStatus(302)でテストできます。
リダイレクトのHTTPレスポンスは302が返ってくるからです。
assertRedirectで/planにリダイレクトするかテストしています。
DBに値があるか確認する方法は、assertDatabaseHasでテストできます。
Unitテスト④更新処理のテスト
app/Http/Controllers/PlanController.php
/**
* プラン更新
*/
public function update(PlanUpdateRequest $request)
{
$this->plan->planUpdate($request);
return Redirect::to('plan');
}
app/Models/Plan.php
/**
* プランの更新
*/
public function planUpdate($request)
{
DB::table($this->table)
->where('plan_id', $request->plan_id)
->where('user_id', $request->user_id)
->update([
'plan_title' => $request->plan_title,
'plan_explanation' => $request->plan_explanation,
'tag_id' => $request->tag_id,
'plan_status' => $request->plan_status,
'amount' => $request->amount,
]);
}
tests/Feature/Plan/PlanUpdateTest.php
<?php
namespace Tests\Feature\Plan;
use App\Models\User;
use App\Models\Tag;
use App\Models\Plan;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Foundation\Testing\WithFaker;
use Tests\TestCase;
class PlanUpdateTest extends TestCase
{
use RefreshDatabase;
/**
* それぞれのメソッドが実行される前に行う処理
*/
public function setUp(): void
{
parent::setUp();
$this->user = User::factory()->create();
$this->tag = Tag::factory()->create();
$this->plan = Plan::factory()->create();
}
/**
* プラン更新処理のテスト
*/
public function test_update_plan()
{
// actingAsでユーザーのログイン認証ができる※プラン編集ページはログイン認証が必要なページ
$this->actingAs($this->user)->get('/plan/edit/' . $this->plan->plan_id);
// プランを更新する内容のパラメータ
$update_plan = [
'plan_id' => $this->plan->plan_id,
'user_id' => $this->plan->user_id,
'tag_id' => $this->tag->tag_id,
'plan_title' => 'プラン更新',
'plan_explanation' => 'プラン更新しました',
'plan_status' => 2,
'amount' => 400,
];
// from指定することで、更新前のページを指定でき、postで更新処理をする
$response = $this->from('/plan/edit/' . $this->plan->plan_id, ['plan' => $this->plan])->post(route('plan.update'), $update_plan);
// 302はリダイレクト成功を表す※プラン一覧ページにリダイレクト成功していることをテスト
$response->assertStatus(302)->assertRedirect('/plan');
// 編集したレコードがデータベースに存在するかテスト※第1引数: テーブル名, 第2引数: カラム値
$this->assertDatabaseHas('plan', ['plan_title' => 'プラン更新']);
}
}
更新処理も登録処理と使うメソッドはほとんど同じです。
冒頭のsetUpもバリデーションで解説しましたが、テストメソッドを実行する前に実行されるもので、ここではテストデータを作成しています。
Unitテスト⑤削除処理のテスト
app/Http/Controllers/PlanController.php
/**
* プランの削除
*/
public function delete($plan_id)
{
$user_id = Auth::user()->user_id;
$params = [
'plan_id' => $plan_id,
'user_id' => $user_id,
];
$this->plan->planDelete($params);
// planDeleteの内容 DB::table($this->table)
->where('plan_id', $params['plan_id'])
->where('user_id', $params['user_id'])
->delete();
return Redirect::to('plan');
}
tests/Feature/Plan/PlanDeleteTest.php
<?php
namespace Tests\Feature\Plan;
use App\Models\User;
use App\Models\Tag;
use App\Models\Plan;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Foundation\Testing\WithFaker;
use Tests\TestCase;
class PlanDeleteTest extends TestCase
{
use RefreshDatabase;
/**
* それぞれのメソッドが実行される前に行う処理
*/
public function setUp(): void
{
parent::setUp();
// プランデータの削除は、plan_idとuser_idが一致するデータを削除するので、明示的に指定
$this->user = User::factory()->create(['user_id' => 1]);
$this->tag = Tag::factory()->create();
$this->plan = Plan::factory()->create(['plan_id' => 1, 'user_id' => 1]);
}
/**
* プラン削除処理のテスト
*/
public function test_delete_plan()
{
// 削除前のデータがDBに存在するか確認するテスト※第1引数: テーブル名, 第2引数: カラム値
$this->assertDatabaseHas('plan', ['plan_id' => $this->plan->plan_id]);
// プランの削除処理
$response = $this->actingAs($this->user)->from(route('plan.editOrDelete'))->delete(route('plan.delete', ['plan_id' => $this->plan->plan_id]));
// 削除後、プラン一覧画面にリダイレクトするか確認するテスト
$response->assertStatus(302)->assertRedirect('/plan');
// 削除後のデータがDBに存在しないか確認するテスト
$this->assertDatabaseMissing('plan', ['plan_id' => $this->plan->plan_id]);
}
}
今回は削除処理をする際に、物理削除するようにしているので、DBからデータが存在しないことを確認するassertDatabaseMissingでテストしています。※詳細は、unitテストのざっくり解説をご確認ください。
休日で空いた時間の暇つぶしを探せるアプリを公開しています。
コメント