はじめに
Laravelでテストを書いていて、テストメソッド同士が互いに影響し合ってしまうケースがありました。
具体的には、別のメソッドで作成したテストデータが、同クラス内の他のメソッドの実行時にも残ってしまっていました。
そんな時は、LaravelのRefreshDatabaseクラスを使用します。
環境
Laravel 9.52.16
問題
- 以下のようなテストを実装した
PurchaseOrdersControllerTest.php
<?php
namespace Tests\Feature;
use App\Models\User;
use Tests\TestCase;
class PurchaseOrdersControllerTest extends TestCase
{
/**
* A basic test example.
*
* @return void
*/
// テストとしてmy/purchase/storeにアクセスできるかどうかを確認
public function test_purchase_store(): void
{
// テスト用のユーザを作成
$user = User::factory()->create();
// テスト用のフォームデータを作成
$formData = [
'title' => 'テストタイトル',
'purchase_abstract' => 'テスト概要',
'category' => 'テストカテゴリー',
'budget' => 1000,
'schedule' => '2021-01-01',
];
// 作成したユーザでアクセス
$response = $this->actingAs($user)->post('/my/purchase/store', $formData);
$response->assertStatus(302);
$this->assertDatabaseHas('purchase_orders', [
'title' => $formData['title'],
'purchase_abstract' => $formData['purchase_abstract'],
'category' => $formData['category'],
'budget' => $formData['budget'],
'schedule' => $formData['schedule'],
]);
}
// テストとしてsales/destroy/{PurchaseId}にアクセスできるかどうかを確認
public function test_purchase_destroy(): void
{
// テスト用のユーザを作成
$user = User::factory()->create();
// テスト用のフォームデータを作成
$formData = [
'title' => 'テストタイトル',
'purchase_abstract' => 'テスト概要',
'category' => 'テストカテゴリー',
'budget' => 1000,
'schedule' => '2021-01-01',
];
// 作成したユーザでフォームデータを保存
$response = $this->actingAs($user)->post('/my/purchase/store', $formData);
$response->assertStatus(302);
$this->assertDatabaseHas('purchase_orders', [
'title' => $formData['title'],
'purchase_abstract' => $formData['purchase_abstract'],
'category' => $formData['category'],
'budget' => $formData['budget'],
'schedule' => $formData['schedule'],
]);
// 作成したフォームデータを取得
$purchase_order = $user->purchase_orders()->first();
// 作成したフォームデータを削除
$response = $this->actingAs($user)->delete('/purchase/destroy/' . $purchase_order->id);
$response->assertStatus(302);
$this->assertDatabaseMissing('purchase_orders', [
'title' => $formData['title'],
'purchase_abstract' => $formData['purchase_abstract'],
'category' => $formData['category'],
'budget' => $formData['budget'],
'schedule' => $formData['schedule'],
]);
}
}
- このテストを実行すると、次のようなエラーが出る
ターミナル
root@:/var/www/html/niches# php artisan test --filter PurchaseOrdersControllerTest
FAIL Tests\Feature\PurchaseOrdersControllerTest
✓ purchase store
⨯ purchase destroy
---
• Tests\Feature\PurchaseOrdersControllerTest > purchase destroy
Failed asserting that a row in the table [purchase_orders] does not match the attributes {
"title": "テストタイトル",
"purchase_abstract": "テスト概要",
"category": "テストカテゴリー",
"budget": 1000,
"schedule": "2021-01-01"
}.
Found similar results: [
{
"title": "テストタイトル",
"purchase_abstract": "テスト概要",
"category": "テストカテゴリー",
"budget": "1000.00",
"schedule": "2021-01-01"
}
].
test_purchase_destroy()により、同メソッド内で保存したデータが削除され、テストが無事パスされることを期待していました。
しかしエラーによると、「データがまだ残っている」とのことでした。
解決法
- test_purchase_store()内のデータが削除されていないため、エラーが出ていた
- そのためRefreshDatabaseを使用し、テストメソッドが実行された後にデータベースをリセットする
- これにより、以前のテストデータが後続のテストに干渉しないようになる
RefreshDatabaseを使う方法は簡単です。
まず名前空間をインポートし、そしてテストクラス内で「use RefreshDatabase」と記述してトレイトの機能を組み込みます。
PurchaseOrdersControllerTest.php(解決案)
<?php
namespace Tests\Feature;
use Illuminate\Foundation\Testing\RefreshDatabase; // ここで
use App\Models\User;
use Tests\TestCase;
class PurchaseOrdersControllerTest extends TestCase
{
use RefreshDatabase;
/**
* A basic test example.
*
* @return void
*/
// テストとしてmy/purchase/storeにアクセスできるかどうかを確認
public function test_purchase_store(): void
{
// 以下略
}
root@:/var/www/html/niches# php artisan test --filter PurchaseOrdersControllerTest
PASS Tests\Feature\PurchaseOrdersControllerTest
✓ purchase store
✓ purchase destroy
Tests: 2 passed
Time: 13.13s
おわりに
「トレイト」という単語も今回初めて知りました。
これは継承と同じくメソッドなどを再利用するために使われるようです。
ただしトレイトはクラスではないため、これを継承したりインスタンス化することはできません。
また、単独で使用することもできません。
管見の限りでは、トレイトの定義に関する記述を公式ドキュメントで見つけることができなかったので、
自分の理解が間違っていればご指摘いただけると助かります…!!
参考