🍰

CakePHP4のFixture Factoriesプラグインの使い方

2023/04/03に公開

はじめに

CakePHP標準のDBのテストは共通のFixtureまたは単位に合わせたFixtureファイルを作成して行います。
大規模になってくるとFixtureファイルが各テストごと作成されたり共有データを意識すると状態管理が難しくなってきます。
それを解決するのが CakePHP Fixture Factories Pluginになります。
このプラグインでは、テストのメッソッドに応じて動的にFixtureを登録できるため管理しやすくなります。

検証環境

  • PHP 7.4
  • CakePHP4.4.12
  • CakePHP Fixture Factories v2.7.1

セットアップ

プラグインのインストール

composer require --dev vierge-noire/cakephp-fixture-factories "^2.5"

プラグインをロードする

src/Application.php
protected function bootstrapCli(): void
{
    // Load more plugins here
    if (Configure::read('debug')) {
        $this->addPlugin('CakephpFixtureFactories');
    }
}

使い方

FixtureFactoryクラスを作成する

bakeコマンドが用意されているので以下のコマンドにより生成します。

bin/cake bake fixture_factory -a

FixtureFactoryはBaseFactoryを継承して利用します。
最低実装しなければならないのは以下の2つのメソッドです。

  • getRootTableRegistryName() Factoryで利用するTable名を示します。
  • setDefaultTemplate() Factory::make()で生成するデフォルトデータを記述します。
class ArticleFactory extends BaseFactory
{
    protected function getRootTableRegistryName(): string
    {
        return 'Articles';
    }
    
    protected function setDefaultTemplate(): void
    {
        $this->setDefaultData(function (Generator $faker) {
            return [
                 'name' => $faker->lastName
            ];
        });
    }
}

テストデータの作成する

CakePHP4.3からFixtureの機能に大きく変更が入りFixtureの戦略を変更することができます。
TruncateDirtyTablesというトレイトが用意されていのでこれを利用いします。
これを設定しない場合、FixtureFactoryで生成されたデータが各テストケース終了時にドロップされません。

public function ArticlesTest extends TestCase
{
    use TruncateDirtyTables;
}

データ利用方法には2パターンあり永続化しないパターン永続化するパターンになります。
永続化しないパターンは永続化前のロジックのテストに最適です。
永続化するパターンはfinderなどのすでに永続化されたものを対象とするテストに用います。

永続化しないパターン

1件のデータを生成する場合は以下のように記述します。

$article = ArticleFactory::make()->getEntity();
$article = ArticleFactory::make(['title' => 'hoge'])->getEntity();

複数生成する場合は以下のように記述します。

$articles = ArticleFactory::make(3)->getEntities();
$articles = ArticleFactory::make(['title' => 'hoge'], 3)->getEntities();

永続化するパターン

1件のデータを生成する場合はgetEntity()の代わりにpersist()呼び出し以下のように記述します。

$article = ArticleFactory::make()->persist();
$article = ArticleFactory::make(['title' => 'hoge'])->persist();

$articles = ArticleFactory::make(3)->persit();
$articles = ArticleFactory::make(['title' => 'hoge'], 3)->persit();

関係モデルのデータを作成する

ルートモデルの関係モデルを同時に生成することも可能です。
関連モデルの生成はwith()メソッドを用います。

ArticleFactory::make(1)->with('Authors', AutherFactory::make(2))->persist()

ArticleFactoryに関連モデルの定義を行っている場合、以下のように書くこともできます。

class ArticleFactory extends BaseFactory
{
    public function withAuthors($parameter = null, int $n = 1): self
    {
        return $this->with('Authors', AuthorFactory::make($parameter, $n));
    }
}
ArticleFactory::make(1)->withAuthors(10)->persist()

再利用可能なデータセットを利用する

標準のFixtureのようにデータを共有して再利用して使いたいということもあるかと思います。
その場合は シナリオ(Scenario) という機能が有効です。
シナリオはApp\Test\Scenarioに配置します。
シナリオクラスはFixtureScenarioInterfaceを適合しload()メソッドを実装します。
例を以下に示します。

class AllStatusArticlesScenario implments FixtureScenarioInterface
{
    public function load($n = 1, ...$args)
    {
        return ArticleFactory::make([['status' => 'draft'], ['status' => 'publish']], $n);
    }
}

テストでシナリオを利用するにはScenarioAwareTraitよりloadFxtureScenario()を呼び出して利用します。
これはステータスのシナリオから公開ステータスのみ抽出するfindPublishedメソッドのテスト例です。


class ArticlesTableTest extends TestCase
{
    use ScenarioAwareTrait;
    
    public function testFindPublished()
    {
        $this->loadFixtureScenario(AllStatusArticlesScenario::class, 1)->persist();
        
        $articles = $this->Articles->find('published')->all();
        $this->assertSame(1, $articles->count());
        
        foreach ($articles as $article) {
            $this->assertSame('publish', $article->status)
        }
    }
}

最後に

いかがだったでしょうか?
今回はFixture Factoriesプラグインの使い方に関して書いてみました。
CakePHP4のFixtureが辛いというかたは試して見ではどうでしょうか。

参考

Discussion