Laravel 8.x データベーステスト

イントロダクション

データベース駆動型アプリケーションのテストを容易にするため、Laravelはさまざまな便利なツールとアサートを提供しています。それに加えて、Laravelモデルのファクトリ(テスト用インスタンスの生成)とシーダ(初期データ設定)により、アプリケーションのEloquentモデルとリレーションを使用し、テストデータベースレコードを簡単に作成できます。これらの強力な機能のすべてについて、以降のドキュメントで説明します。

各テスト後のデータベースリセット

先へ進む前に、以前のテストデータが後続のテストに干渉しないように、各テストの後にデータベースをリセットする方法について説明しましょう。Laravelに含まれているIlluminate\Foundation\Testing\RefreshDatabaseトレイトがこれを処理します。テストクラスでトレイトを使用するだけです。

<?php

namespace Tests\Feature;

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

class ExampleTest extends TestCase
{
    use RefreshDatabase;

    /**
     * 基本的な機能テスト例
     *
     * @return void
     */
    public function test_basic_example()
    {
        $response = $this->get('/');

        // ...
    }
}

モデルファクトリの定義

コンセプトの概要

まず、Eloquentモデルファクトリについて説明しましょう。テストするときは、テストを実行する前に、データベースにいくらかのレコードを挿入する必要があります。このテストデータを作成するときに各カラムの値を自分でいちいち指定する代わりに、Laravelではモデルファクトリを使用し、各Eloquentモデルのデフォルト属性を定義できます。

ファクトリの作成方法の例を確認するには、アプリケーションのdatabase/factorys/UserFactory.phpファイルを見てください。このファクトリはすべての新しいLaravelアプリケーションに含まれており、以下のファクトリ定義が含まれています。

namespace Database\Factories;

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

class UserFactory extends Factory
{
    /**
     * ファクトリの対応するモデル名
     *
     * @var string
     */
    protected $model = User::class;

    /**
     * モデルのデフォルト状態の定義
     *
     * @return array
     */
    public function definition()
    {
        return [
            'name' => $this->faker->name(),
            'email' => $this->faker->unique()->safeEmail(),
            'email_verified_at' => now(),
            'password' => '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', // password
            'remember_token' => Str::random(10),
        ];
    }
}

ご覧のとおり、一番基本的な形式では、ファクトリはLaravelの基本ファクトリクラスを拡張し、modelプロパティとdefinitionメソッドを定義するクラスです。definitionメソッドは、ファクトリを使用してモデルを作成するときに適用する必要がある属性値のデフォルトセットを返します。

ファクトリはfakerプロパティを介して、Faker PHPライブラリにアクセスできます。これにより、テスト用のさまざまな種類のランダムデータを簡単に生成できます。

Tip!! config/app.php設定ファイルにfaker_localeオプションを追加することで、アプリケーションのFakerロケールを設定できます。

ファクトリの生成

ファクトリを作成するには、make:factory Artisanコマンドを実行します。

php artisan make:factory PostFactory

新しいファクトリクラスは、database/factoriesディレクトリに配置されます。

--modelオプションは、ファクトリにより作成するモデルの名前を指定するために使用します。このオプションは、生成するファクトリファイルへ指定するモデルを事前に挿入します。

php artisan make:factory PostFactory --model=Post

ファクトリの状態

状態操作メソッドを使用すると、モデルファクトリへ任意の組み合わせで適用できる個別の変更を定義できます。たとえば、Database\Factories\UserFactoryファクトリに、デフォルトの属性値の1つを変更するsuspended状態メソッドが含まれているとしましょう。

状態変換メソッドは通常、Laravelの基本ファクトリクラスが提供するstateメソッドを呼び出します。stateメソッドは、このファクトリ用に定義する素の属性の配列を受け取るクロージャを受け入れ、変更する属性の配列を返す必要があります。

/**
 * ユーザーが一時停止されていることを示す
 *
 * @return \Illuminate\Database\Eloquent\Factories\Factory
 */
public function suspended()
{
    return $this->state(function (array $attributes) {
        return [
            'account_status' => 'suspended',
        ];
    });
}

ファクトリのコールバック

ファクトリコールバックは、afterMakingメソッドとafterCreatingメソッドを使用して登録し、モデルの作成または作成後に追加のタスクを実行できるようにします。ファクトリクラスでconfigureメソッドを定義して、これらのコールバックを登録する必要があります。ファクトリがインスタンス化されるときにLaravelが自動的にこのメソッドを呼び出します。

namespace Database\Factories;

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

class UserFactory extends Factory
{
    /**
     * ファクトリの対応するモデル名
     *
     * @var string
     */
    protected $model = User::class;

    /**
     * モデルファクトリの設定
     *
     * @return $this
     */
    public function configure()
    {
        return $this->afterMaking(function (User $user) {
            //
        })->afterCreating(function (User $user) {
            //
        });
    }

    // ...
}

ファクトリを使用するモデル生成

モデルのインスタンス化

ファクトリを定義したら、そのモデルのファクトリインスタンスをインスタンス化するために、Illuminate\Database\Eloquent\Factories\HasFactoryトレイトにより、モデルが提供する静的なfactoryメソッドを使用できます。モデル作成のいくつかの例を見てみましょう。まず、makeメソッドを使用して、データベースへ永続化せずにモデルを作成します。

use App\Models\User;

public function test_models_can_be_instantiated()
{
    $user = User::factory()->make();

    // テストでモデルを使用する
}

countメソッドを使用して多くのモデルのコレクションを作成できます。

$users = User::factory()->count(3)->make();

状態の適用

状態のいずれかをモデルに適用することもできます。モデルへ複数の状態変換を適用する場合は、状態変換メソッドを直接呼び出すだけです。

$users = User::factory()->count(5)->suspended()->make();

属性のオーバーライド

モデルのデフォルト値の一部をオーバーライドしたい場合は、値の配列をmakeメソッドに渡してください。指定された属性のみが置き換えられ、残りの属性はファクトリで指定したデフォルト値へ設定したままになります。

$user = User::factory()->make([
    'name' => 'Abigail Otwell',
]);

もしくは、stateメソッドをファクトリインスタンスで直接呼び出して、インライン状態変更を実行することもできます。

$user = User::factory()->state([
    'name' => 'Abigail Otwell',
])->make();

Tip!! 複数代入保護は、ファクトリを使用してのモデル作成時、自動的に無効になります。

ファクトリとモデルの接続

HasFactoryトレイトのfactoryメソッドは、規約を使用してモデルの適切なファクトリを決定します。具体的にこのメソッドは、モデル名と一致するクラス名を持ち、接尾辞が「Factory」である、Database\Factories名前空間内のファクトリを検索します。この規約を特定のアプリケーションまたはファクトリに適用しない場合は、モデルのnewFactoryメソッドを上書きして、モデルの対応するファクトリのインスタンスを直接返すことができます。

use Database\Factories\Administration\FlightFactory;

/**
 * モデルの新しいファクトリインスタンスの作成
 *
 * @return \Illuminate\Database\Eloquent\Factories\Factory
 */
protected static function newFactory()
{
    return FlightFactory::new();
}

モデルの永続化

createメソッドはモデルインスタンスをインスタンス化し、Eloquentのsaveメソッドを使用してデータベースへ永続化します。

use App\Models\User;

public function test_models_can_be_persisted()
{
    // App\Models\Userインスタンスを一つ作成
    $user = User::factory()->create();

    // App\Models\Userインスタンスを3つ作成
    $users = User::factory()->count(3)->create();

    // テストでモデルを使用する…
}

属性の配列をcreateメソッドに渡すことで、ファクトリのデフォルトのモデル属性をオーバーライドできます。

$user = User::factory()->create([
    'name' => 'Abigail',
]);

連続データ

モデルを生成するごとに、特定のモデル属性の値を変更したい場合があります。これは、状態変換を連続データとして定義することで実現できます。たとえば、作成されたユーザーごとに、adminカラムの値をYNの間で交互に変更したいとしましょう。

use App\Models\User;
use Illuminate\Database\Eloquent\Factories\Sequence;

$users = User::factory()
                ->count(10)
                ->state(new Sequence(
                    ['admin' => 'Y'],
                    ['admin' => 'N'],
                ))
                ->create();

この例では、admin値がYのユーザーが5人作成され、admin値がNのユーザーが5人作成されます。

必要に応じて、シーケンス値としてクロージャを含めることができます。新しい値をそのシーケンスが必要とするたびにクロージャを呼び出します。

$users = User::factory()
                ->count(10)
                ->state(new Sequence(
                    fn () => ['role' => UserRoles::all()->random()],
                ))
                ->create();

リレーションのファクトリ

Has Manyリレーション

次に、Laravelの流暢(fluent)なファクトリメソッドを使用して、Eloquentモデルのリレーションを構築する方法を見ていきましょう。まず、アプリケーションにApp\Models\UserモデルとApp\Models\Postモデルがあると想定します。また、UserモデルがPostとのhasManyリレーションを定義していると想定しましょう。 Laravelのファクトリが提供するhasメソッドを使用して、3つの投稿を持つユーザーを作成できます。hasメソッドはファクトリインスタンスを引数に取ります。

use App\Models\Post;
use App\Models\User;

$user = User::factory()
            ->has(Post::factory()->count(3))
            ->create();

規約により、Postモデルをhasメソッドに渡す場合、LaravelはUserモデルにリレーションを定義するpostsメソッドが存在していると想定します。必要に応じ、操作するリレーション名を明示的に指定できます。

$user = User::factory()
            ->has(Post::factory()->count(3), 'posts')
            ->create();

もちろん、関連モデルで状態を操作することもできます。さらに、状態変更で親モデルへのアクセスが必要な場合は、クロージャベースの状態変換が渡せます。

$user = User::factory()
            ->has(
                Post::factory()
                        ->count(3)
                        ->state(function (array $attributes, User $user) {
                            return ['user_type' => $user->type];
                        })
            )
            ->create();

マジックメソッドの使用

使いやすいように、Laravelのマジックファクトリリレーションメソッドを使用してリレーションを構築できます。たとえば、以下の例では、規約を使用して、Userモデルのpostsリレーションメソッドを介して作成する必要がある関連モデルを決定します。

$user = User::factory()
            ->hasPosts(3)
            ->create();

マジックメソッドを使用してファクトリリレーションを作成する場合、属性の配列を渡して、関連モデルをオーバーライドできます。

$user = User::factory()
            ->hasPosts(3, [
                'published' => false,
            ])
            ->create();

状態の変更で親モデルへのアクセスが必要な場合は、クロージャベースの状態変換を提供できます。

$user = User::factory()
            ->hasPosts(3, function (array $attributes, User $user) {
                return ['user_type' => $user->type];
            })
            ->create();

Belongs Toリレーション

ファクトリを使用して"has many"リレーションを構築する方法を検討したので、逆の関係を調べてみましょう。forメソッドを使用して、ファクトリが作成したモデルの属する親モデルを定義できます。たとえば、1人のユーザーに属する3つのApp\Models\Postモデルインスタンスを作成できます。

use App\Models\Post;
use App\Models\User;

$posts = Post::factory()
            ->count(3)
            ->for(User::factory()->state([
                'name' => 'Jessica Archer',
            ]))
            ->create();

作成するモデルに関連付ける必要のある親モデルインスタンスがすでにある場合は、モデルインスタンスをforメソッドに渡すことができます。

$user = User::factory()->create();

$posts = Post::factory()
            ->count(3)
            ->for($user)
            ->create();

マジックメソッドの使用

便利なように、Laravelのマジックファクトリリレーションシップメソッドを使用して、"belongs to"リレーションシップを定義できます。たとえば、以下の例では、3つの投稿がPostモデルのuserリレーションに属する必要があることを規約を使用して決定しています。

$posts = Post::factory()
            ->count(3)
            ->forUser([
                'name' => 'Jessica Archer',
            ])
            ->create();

Many To Manyリレーション

has manyリレーションと同様に、"many to many"リレーションは、hasメソッドを使用して作成できます。

use App\Models\Role;
use App\Models\User;

$user = User::factory()
            ->has(Role::factory()->count(3))
            ->create();

ピボットテーブルの属性

モデルをリンクするピボット/中間テーブルへ設定する属性を定義する必要がある場合は、hasAttachedメソッドを使用します。このメソッドは、ピボットテーブルの属性名と値の配列を2番目の引数に取ります。

use App\Models\Role;
use App\Models\User;

$user = User::factory()
            ->hasAttached(
                Role::factory()->count(3),
                ['active' => true]
            )
            ->create();

状態変更で関連モデルへのアクセスが必要な場合は、クロージャベースの状態変換を指定できます。

$user = User::factory()
            ->hasAttached(
                Role::factory()
                    ->count(3)
                    ->state(function (array $attributes, User $user) {
                        return ['name' => $user->name.' Role'];
                    }),
                ['active' => true]
            )
            ->create();

作成しているモデルへアタッチしたいモデルインスタンスがすでにある場合は、モデルインスタンスをhasAttachedメソッドへ渡せます。この例では、同じ3つの役割が3人のユーザーすべてに関連付けられます。

$roles = Role::factory()->count(3)->create();

$user = User::factory()
            ->count(3)
            ->hasAttached($roles, ['active' => true])
            ->create();

マジックメソッドの使用

利便性のため、Laravelのマジックファクトリリレーションメソッドを使用して、多対多のリレーションを定義できます。たとえば、次の例では、関連するモデルをUserモデルのrolesリレーションメソッドを介して作成する必要があることを規約を使用して決定します。

$user = User::factory()
            ->hasRoles(1, [
                'name' => 'Editor'
            ])
            ->create();

ポリモーフィックリレーション

ポリモーフィックな関係もファクトリを使用して作成できます。ポリモーフィックな"morph many"リレーションは、通常の"has many"リレーションと同じ方法で作成します。たとえば、 App\Models\PostモデルがApp\Models\CommentモデルとmorphMany関係を持っている場合は以下のようになります。

use App\Models\Post;

$post = Post::factory()->hasComments(3)->create();

Morph Toリレーション

マジックメソッドを使用してmorphTo関係を作成することはできません。代わりに、forメソッドを直接使用し、関係の名前を明示的に指定する必要があります。たとえば、CommentモデルにmorphTo関係を定義する commentableメソッドがあると想像してください。この状況でforメソッドを直接使用し、1つの投稿に属する3つのコメントを作成できます。

$comments = Comment::factory()->count(3)->for(
    Post::factory(), 'commentable'
)->create();

ポリモーフィック多対多リレーション

ポリモーフィック「多対多」(morphToManymorphedByMany)リレーションは、ポリモーフィックではない「多対多」リレーションと同じように作成できます。

use App\Models\Tag;
use App\Models\Video;

$videos = Video::factory()
            ->hasAttached(
                Tag::factory()->count(3),
                ['public' => true]
            )
            ->create();

もちろん、hasマジックメソッドを使用して、ポリモーフィックな「多対多」リレーションを作成することもできます。

$videos = Video::factory()
            ->hasTags(3, ['public' => true])
            ->create();

ファクトリ内でのリレーション定義

モデルファクトリ内でリレーションを定義するには、リレーションの外部キーへ新しいファクトリインスタンスを割り当てます。これは通常、belongsTomorphToリレーションなどの「逆」関係で行います。たとえば、投稿を作成時に新しいユーザーを作成する場合は、次のようにします。

use App\Models\User;

/**
 * モデルのデフォルト状態の定義
 *
 * @return array
 */
public function definition()
{
    return [
        'user_id' => User::factory(),
        'title' => $this->faker->title,
        'content' => $this->faker->paragraph,
    ];
}

リレーションのカラムがそれを定義するファクトリに依存している場合は、属性にクロージャを割り当てることができます。クロージャは、ファクトリの評価済み属性配列を受け取ります。

/**
 * モデルのデフォルト状態の定義
 *
 * @return array
 */
public function definition()
{
    return [
        'user_id' => User::factory(),
        'user_type' => function (array $attributes) {
            return User::find($attributes['user_id'])->type;
        },
        'title' => $this->faker->title,
        'content' => $this->faker->paragraph,
    ];
}

シーダの実行

機能テスト中にデータベースシーダ(初期値設定)を使用してデータベースへデータを入力する場合は、seedメソッドを呼び出してください。seedメソッドはデフォルトで、DatabaseSeederを実行します。これにより、他のすべてのシーダが実行されます。または、特定のシーダクラス名をseedメソッドに渡します。

<?php

namespace Tests\Feature;

use Database\Seeders\OrderStatusSeeder;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Foundation\Testing\WithoutMiddleware;
use Tests\TestCase;

class ExampleTest extends TestCase
{
    use RefreshDatabase;

    /**
     * 新しい注文の作成をテスト
     *
     * @return void
     */
    public function test_orders_can_be_created()
    {
        // DatabaseSeederを実行
        $this->seed();

        // 特定のシーダを実行
        $this->seed(OrderStatusSeeder::class);

        // ...
    }
}

または、各テストの前にデータベースのシーダを自動的に実行するよう、RefreshDatabaseトレイトへ指示することもできます。これはテストクラスで$seedプロパティを定義することで行います。

<?php

namespace Tests\Feature;

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

class ExampleTest extends TestCase
{
    /**
     * デフォルトのシーダーが各テストの前に実行するかを示す
     *
     * @var bool
     */
    protected $seed = true;

    // ...
}

$seedプロパティがtrueの場合、テストは各テストの前にDatabase\Seeders\DatabaseSeederクラスを実行します。ただし、テストクラスに$seederプロパティを定義して、実行する必要がある特定のシーダーを指定できます。

use Database\Seeders\OrderStatusSeeder;

/**
 * 各テストの前に特定のシーダーを実行
 *
 * @var string
 */
protected $seeder = OrderStatusSeeder::class;

利用可能なアサート

Laravelは、PHPUnit機能テスト用にいくつかのデータベースアサートを提供しています。以下にこのようなアサートについて説明します。

assertDatabaseCount

データベース内のテーブルに指定した数のレコードが含まれていることをアサートします。

$this->assertDatabaseCount('users', 5);

assertDatabaseHas

データベース内のテーブルに、指定したキー/値クエリの制約に一致するレコードが含まれていることをアサートします。

$this->assertDatabaseHas('users', [
    'email' => 'sally@example.com',
]);

assertDatabaseMissing

データベース内のテーブルに、指定したキー/値クエリの制約に一致するレコードが含まれていないことをアサートします。

$this->assertDatabaseMissing('users', [
    'email' => 'sally@example.com',
]);

assertDeleted

assertDeletedは、指定したEloquentモデルがデータベースから削除されたことをアサートします。

use App\Models\User;

$user = User::find(1);

$user->delete();

$this->assertDeleted($user);

assertSoftDeletedメソッドは、指定したEloquentモデルが「ソフト削除」されたことをアサートします。

$this->assertSoftDeleted($user);

ドキュメント章別ページ

ヘッダー項目移動

注目:アイコン:ページ内リンク設置(リンクがないヘッダーへの移動では、リンクがある以前のヘッダーのハッシュをURLへ付加します。

移動

クリックで即時移動します。

設定

適用ボタンクリック後に、全項目まとめて適用されます。

カラーテーマ
和文指定 Pagination
和文指定 Scaffold
Largeスクリーン表示幅
インデント
本文フォント
コードフォント
フォント適用確認

フォントの指定フィールドから、フォーカスが外れると、当ブロックの内容に反映されます。EnglishのDisplayもPreviewしてください。

フォント設定時、表示に不具合が出た場合、当サイトのクッキーを削除してください。

バックスラッシュを含むインライン\Code\Blockの例です。

以下はコードブロックの例です。

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class User extends Model
{
    /**
     * ユーザに関連する電話レコードを取得
     */
    public function phone()
    {
        return $this->hasOne('App\Phone');
    }
}

設定を保存する前に、表示が乱れないか必ず確認してください。CSSによるフォントファミリー指定の知識がない場合は、フォントを変更しないほうが良いでしょう。

キーボード・ショートカット

オープン操作

PDC

ページ(章)移動の左オフキャンバスオープン

HA

ヘッダー移動モーダルオープン

MS

移動/設定の右オフキャンバスオープン

ヘッダー移動

T

最初のヘッダーへ移動

E

最後のヘッダーへ移動

NJ

次ヘッダー(H2〜H4)へ移動

BK

前ヘッダー(H2〜H4)へ移動

その他

?

このヘルプページ表示
閉じる