🚀

【Laravel】PHPUnit:テストデータ生成とDATE型カラムのエラー対処

2023/10/01に公開

はじめに

PHPUnitのテストコードについて新しく知見が増えたため、
備忘録として残します。

factoryでテストデータを生成する

factoryを使用することで、テスト時にマスターデータを作成するなど
テストデータを生成することができます。

テストデータ生成

php artisan make:factory CategoryFactoryコマンドで作成したfactoryに
以下のようにカラム名と値を定義します。(カテゴリーの例)

CategoryFactory.php
class CategoryFactory extends Factory
{
    // カテゴリーモデルが対象
    protected $model = Category::class;
    
    public function definition(): array
    {
        return [
            'name' => $this->faker->word,
        ];
    }
}

factoryを呼び出し

そして、テストコードでは以下のようにfactoryを呼び出すのみです。

テスト時にカテゴリーのレコードを登録し、それを後の処理で使用でき、
また、count()により複数のレコードを登録することもできます。

CreateTaskTest.php
    public function create_task_success()
    {
        // カテゴリーのレコードを一つ登録
        $category = Category::factory()->create();
	// 複数のレコードを登録
	$categories = Category::factory()->count(3)->create();
	
	// 処理
	// ...
	$response = $this->postJson('/api/tasks', $data);
    }

unique

unique()は、Factoryで生成されるデータが重複しないようにします。
以下の例では```unique()``を使用し、ユニークなnameを生成しています。

CategoryFactory.php
public function definition(): array
{
return [
    'name' => $this->faker->unique()->word,
];
}

このメソッドは、DBのユニーク制約が設定されているカラムに対して役に立ちます。
例えば、例のfactory()->count(3)->create()を用いて
一度に複数のレコードを作成する際に、ユニーク制約違反によるエラーを防ぐことができます。

もしunique()を使用しない場合、重複による失敗は毎回ではなく時々起きるため、
気づくのが遅れる可能性があります。
※例えばdayOfWeek()のように、生成する曜日の選択肢が限られている場合、重複エラーが発生しやすくなります。

そのため、ユニーク制約のカラムではunique()を使用することが望ましいです。

DATE型とキャスト:エラーの原因と対策

タスクの登録テスト時にassertDatabaseHas()を使い、以下のようにデータが登録されているかを確認していました。

$data = [
    'category_id' => 3,
    'title' => 'タスクテスト',
    'start_date' => '2023-09-28',
];

// POST送信
$response = $this->postJson('/api/tasks', $data);

// データベースに正しいデータが保存されているか検証
$this->assertDatabaseHas('tasks', [
    'category_id' => $data['category_id'],
    'title' => $data['title'],
    'start_date' => $data['start_date'],
]);

POST先では特に値の変換はしていないため、成功するかと思っていましたが、
assertDatabaseHasで「start_date」カラムにエラーが発生しました。

原因

start_dateはDATE型のカラムであり、以下のように
キャストでdate属性に型変換されるデータのためエラーが発生しました。

class Task extends Model
{
    protected $casts = [
        'start_date' => 'date',
    ];
}

キャストにより、DBから「start_date」のデータを取得時にCarbonインスタンスとして取得されるため、文字列としてフォーマットすると、時間部分が「00:00:00」として表示されます。
※DBでは、時間部分はなく日付のみが保存されている

そのため、start_dateの値は「2023-09-28」でPOST送信しているにも関わらず、
assertDatabaseHasの検証時に、「2023-09-28 00:00:00」となることによりエラーが発生していました。

解決

これは、「start_date」のみassertDatabaseHasから外し、
新たに取得した「start_date」を「Y-m-d」の形式に変換し、比較することで解決できます。

// 処理 ...
$response = $this->postJson('/api/tasks', $data);

// データベースに正しいデータが保存されているか検証
$this->assertDatabaseHas('tasks', [
    'category_id' => $data['category_id'],
    'title' => $data['title'],
]);

// toDateString()で「Y-m-d」の形式にする
$task = Task::first();
$this->assertEquals($data['start_date'], $task->start_date->toDateString());

日付や時間の検証は少し複雑なため、他のデータとは別に検証することも考えてもいいかと思いました。

補足

以下のエラーは、テストファイルを「Unit」で作成し、テストを行った際に発生しました。

A facade root has not been set.

Unitを作成する際のデフォルトでは、use PHPUnit\Framework\TestCaseとなっていますが、これはPHPUnitのクラスです。
そのため、ファザードやヘルパー関数など、Laravel特有の機能を使用できず、エラーが発生します。

この問題は、use Tests\TestCaseに変更することで解決でき、これにより、Laravel特有の機能が使用できるようになります。

Discussion