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

イントロダクション

Laravelでは、データベースを駆動するアプリケーションのテストを簡単にできる、便利で数多くのツールを用意しています。その一つは、指定した抽出条件に一致するデータがデータベース中に存在するかをアサートする、assertDatabaseHasヘルパです。たとえば、usersテーブルの中にemailフィールドがsally@example.comの値のレコードが存在するかを確認したいとしましょう。次のようにテストできます。

public function testDatabase()
{
    // アプリケーションを呼び出す…

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

データベースにデータが存在しないことをアサートする、assertDatabaseMissingヘルパを使うこともできます。

assertDatabaseHasメソッドやその他のヘルパは、皆さんが便利に使ってもらうため用意しています。PHPUnitの組み込みアサートメソッドは、機能テストで自由に使用できます。

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

前のテストがその後のテストデータに影響しないように、各テストの後にデータベースをリセットできると便利です。インメモリデータベースを使っていても、トラディショナルなデータベースを使用していても、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 testBasicExample()
    {
        $response = $this->get('/');

        // …
    }
}

ファクトリの生成

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

ファクトリを生成するには、make:factory Artisanコマンドを使用します。

php artisan make:factory PostFactory

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

--modelオプションにより、ファクトリが生成するモデルの名前を指定できます。このオプションは、生成するファクトリファイルへ指定モデル名を事前に設定します。

php artisan make:factory PostFactory --model=Post

ファクトリの記述

To get started, take a look at the database/factories/UserFactory.php file in your application. Out of the box, this file contains the following factory definition:開始前にアプリケーション中のdatabase/factories/UserFactory.phpファイルをご覧ください。始めから、このファイルは以下のファクトリ定義を含んでいます。

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!! Fakerのローケルは、config/app.php設定ファイルのfaker_localeオプションで指定できます。

ファクトリステート

ステート操作メソッドにより、モデルファクトリのどんな組み合わせに対しても適用できる、個別の調整を定義できます。たとえば、Userモデルは、デフォルト属性値の一つを変更する、suspended状態を持つとしましょう。stateメソッドを使い、状態遷移を定義します。ステートメソッドには好きな名前が付けられます。典型的なPHPメソッドにすぎません。

/**
 * そのユーザーが資格保留(suspended)されていることを表す
 *
 * @return \Illuminate\Database\Eloquent\Factories\Factory
 */
public function suspended()
{
    return $this->state([
        'account_status' => 'suspended',
    ]);
}

状態遷移がファクトリで定義した他の属性にアクセする必要がある場合は、stateメソッドへコールバックを渡してください。そのコールバックはファクトリで定義したそのままの属性の配列を受け取ります。

/**
 * そのユーザーが資格保留(suspended)されていることを表す
 *
 * @return \Illuminate\Database\Eloquent\Factories\Factory
 */
public function suspended()
{
    return $this->state(function (array $attributes) {
        return [
            'account_status' => 'suspended',
        ];
    });
}

ファクトリコールバック

ファクトリコールバックはafterMakingafterCreatingメソッドを使用し登録し、モデルを作成、もしくは生成した後の追加タスクを実行できるようにします。これらのコールバックは、ファクトリクラスの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) {
            //
        });
    }

    // …
}

ファクトリの使用

モデルの生成

ファクトリを定義できたら、モデルのファクトリインスタンスをインスタンス化するため、Eloquentモデル上のIlluminate\Database\Eloquent\Factories\HasFactoryトレイトが提供している静的factoryメソッドを使います。

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

class User extends Model
{
    use HasFactory;
}

モデルの生成例をいくつか見てみましょう。データベースへ保存せずにモデルを生成するmakeメソッドを使ってみましょう。

use App\Models\User;

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

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

You may create a collection of many models using the count method:

// Create three App\Models\User instances...
$users = User::factory()->count(3)->make();

HasFactoryトレイトのfactoryメソッドはモデルに対し正しいファクトリなのかを判定するために便利に使えます。具体的には、このメソッドはDatabase\Factories名前空間の中のモデル名と一致するクラス名を持ち、最後にFactoryが付くファクトリを探します。この命名規則を特定のアプリケーションまたはファクトリで適用しない場合は、ファクトリを直接使用してモデルインスタンスを作成できます。ファクトリクラスを使用して新しいファクトリインスタンスを作成するには、ファクトリで静的なnewメソッドを呼び出す必要があります

/**
 * Create a new factory instance for the model.
 *
 * @return \Illuminate\Database\Eloquent\Factories\Factory
 */
protected static function newFactory()
{
    return \Database\Factories\Administration\FlightFactory::new();
}

ステートの適用

こうしたモデルに対してステートを適用することもできます。複数の状態遷移を適用したい場合は、シンプルにステートメソッドを直接呼び出します。

$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!! ファクトリを用いモデルを生成する場合は、複数代入の保護を自動的に無効にします。

モデルの保存

createメソッドはモデルインスタンスを生成するだけでなく、Eloquentのsaveメソッドを使用しデータベースへ保存します。

use App\Models\User;

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

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

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

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

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

順序

作成する各モデルごとに、指定するモデル属性の値を交互に指定したい場合もあります。それには状態遷移をSequenceインスタンスとして定義します。たとえば、Userモデルの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ユーザーとNの5ユーザーが生成されます。

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

定義中のリレーション

ファクトリ定義の中でモデルへのリレーションを付加できます。例としてPost作成時に新しいUserインスタンスを作成したいとしましょう。以下のようになります。

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,
    ];
}

Has Manyリレーション

次に、Laravelの読み書きしやすいファクトリメソッドを使用して、Eloquentモデルリレーションの構築を説明します。まず、アプリケーションにUserPostモデルがあると仮定しましょう。そのUserモデルはPostに対してhasManyリレーションを定義しているとも仮定しましょう。ファクトリが提供するhasメソッドを使い、3ポストを持つユーザーを1件作ってみます。hasメソッドはファクトリインスタンスを引数に取ります。

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

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

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

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

もちろん、関連するモデルに対し状態操作することもできます。加えて、状態の変更に親モデルへのアクセスが必要であるなら、クロージャベースで状態遷移を渡すこともできます。

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

マジックメソッドの使用

リレーションシップを定義するため便利なように、ファクトリのマジックリレーションメソッドを使用できます。たとえば以下の例では、関連するモデルがUserモデル上のpostsリレーションメソッドを介して作成されるべきであることを決定するように記法を使用します。

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

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

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

状態の変更で親モデルにアクセスする必要があるなら、クロージャベースの状態遷移を渡せます。

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

Belongs Toリレーション

今度はファクトリを使用した"has many"リレーションをどのように構築するか説明します。forメソッドは、ファクトリで作成されたモデルが属するモデルを定義するために使われます。たとえば、1人のユーザーに属する3つのPostモデルインスタンスを作成できます。

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

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

マジックメソッドの使用

"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;

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

中間テーブルの属性

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

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

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

状態変化で関連モデルへアクセスする必要があれば、クロージャベースの状態遷移を指定できます。

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

マジックメソッドの使用

ファクトリのマジックリレーションメソッドを使用して、多対多のリレーションを便利に定義できます。たとえば次の例では、関連するモデルがUserモデル上のrolesリレーションメソッドを介して作成されるべきだと決めるため記法を使用しています。

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

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

ポリモーフィックリレーションもファクトリを使って作成できます。ポリモーフィックな"morph many"リレーションは、典型的な "has many"リレーションと同じ方法で作成します。たとえば、Postモデルが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();

Polymorphic Many To Manyリレーション

ポリモーフィック"many to many"リレーションは、ポリモーフィックではない"many to many"と同様に作成できます。

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

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

もちろん、マジックhasメソッドも、ポリモーフィック"many to many"リレーションを作成するために使用できます。

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

シーダの使用

機能テストでデータベースへ初期値を設定するために、データベースシーダを使いたい場合は、seedメソッドを使用してください。デフォルトでseedメソッドは、他のシーダを全部実行するDatabaseSeederを返します。もしくは、seedメソッドへ特定のシーダクラス名を渡してください。

<?php

namespace Tests\Feature;

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

class ExampleTest extends TestCase
{
    use RefreshDatabase;

    /**
     * 新オーダー生成のテスト
     *
     * @return void
     */
    public function testCreatingANewOrder()
    {
        // DatabaseSeederを実行
        $this->seed();

        // シーダを1つ実行
        $this->seed(OrderStatusSeeder::class);

        // …
    }
}

使用可能なアサーション

Laravelは、多くのデータベースアサーションをPHPUnit機能テスト向けに提供しています。

メソッド 説明
$this->assertDatabaseCount($table, int $count); データベースのテーブルが、エンティティを指定量含むことをアサート
$this->assertDatabaseHas($table, array $data); 指定したデータが、テーブルに存在することをアサート
$this->assertDatabaseMissing($table, array $data); 指定したデータが、テーブルに含まれないことをアサート
$this->assertDeleted($table, array $data); 指定したレコードが削除されていることをアサート
$this->assertSoftDeleted($table, array $data); 指定したレコードがソフトデリートされていることをアサート

レコードの削除・ソフト削除のアサートに便利なよう、assertDeletedassertSoftDeletedヘルパへはモデルが渡せるようになっています。その場合、モデルの主キーを利用します。

たとえば、モデルファクトリをテストで使用する場合に、アプリケーションでデータベースからそのレコードが確実に削除されているかをテストするために、これらのヘルパへモデルを渡せます。

public function testDatabase()
{
    $user = User::factory()->create();

    // アプリケーションを呼び出す…

    $this->assertDeleted($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)へ移動

その他

?

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