🐥

LaravelでInertia.jsを使ったテストの時にAssertを拡張して同時に2つの値を検証する方法

2023/04/16に公開

はじめに

Laravel と Inertia.js を使って開発をしている Web エンジニアです。最近、検索機能のテストを行う必要があったのですが、JSON から 2 つの値を同時に検証したり、or 条件で検証することができないという問題が発生しました。そこで、私が見つけたいくつかの解決方法について、この記事で紹介したいと思います。

前提条件

アカウントがありfirst_namelast_nameをたとえばJohnで検索するときにサーバーではfirst_namelast_namefull_nameとして部分一致検索します。
その結果を以下の JSON 構造でフロントに返します。

"accounts": [
    {
        "first_name": "John",
        "last_name": "Doe"
    },
    {
        "first_name": "Lorem",
        "last_name": "John"
    },
    {
        "first_name": "John",
        "last_name": "Clare"
    }
]

検索のテスト結果を検証するためにfirst_nameもしくはlast_nameのどちらかに検索した文字列が含まれているかを検証する必要があります。
しかし、AssertableInertia のクラスとその他依存クラスには 1 つの account の 2 つの値を同時に検証する関数もしくは or 条件でどちらか成立することを検証する関数がない。

解決方法

where で accounts を一回で検証する

最初に思いついた方法としてはaccountsを一回で検証する方法です。
以下のようにaccountsmap()first_namelast_nameを結合したfull_nameの Collection を生成してから、全ての名前に検索文字列が含まれているかを検証しています。

$this->get('/accounts?filter[name]=John')
    ->assertInertia(fn (Assert $page) => $page
        ->where('accounts', function (Collection $accounts) use ($searchName) {
            $fullNames = $accounts->map(fn ($account) => $account['first_name'] . $account['last_name']);
            return $fullNames->every(fn ($fullName) => Str::contains($fullName, $searchName));
        })
        )
    );

each で account を 1 つずつ検証する

最初のやり方でも良かったのですが、自然ではない気がしたため 1 つずつ検証する方法を検討しました。
each を使うことで accounts の中身を 1 つずつ検証できます。
やることは同じでfirst_namelast_nameを結合してfull_nameにしてから検証することです。この時に AssertableInertia クラスを覗くとtoArray()を持っていることがわかるのでそこからprops.first_nameprops.last_nameを取得します。
最後に PHPUnit の検証関数を使って文字列の検証を行い、etc でテストを終了させます。

$this->get('/accounts?filter[name]=John')
    ->assertInertia(fn (Assert $page) => $page
        ->has('accounts', fn(Assert $page) => $page
            ->each(function (Assert $page) use ($searchName) {
                $fullName = $page->toArray()['props']['first_name'] . $page->toArray()['props']['last_name'];
                \PHPUnit\Framework\Assert::assertStringContainsString($searchName, $fullName);
                $page->etc();
            })
        )
    );

Macro で AssertInertia クラスを拡張する

今回採用した方法で、一番いいと思った方法です。
AssertableInertiaクラスを確認するとAssertableJsonを継承していることがわかります。
AssertableJsonMacroableを use しているので macro を使って拡張することができ、例えば AppServiceProvider の boot にwhereSomeContainsString()という新しい assert 関数を追加します。
内容としては AssertableInertia がアクセス可能なprop()関数から JSON のレスポンスを取得して渡されたキー(first_name, last_name)のみを抽出してからどれが検索文字列を含んでいるかを検証しています。どちらも含んでいない場合は[first_name, last_name] did not contain [John]のメッセージと共にテスト失敗になります。

use Illuminate\Support\Arr;
use Illuminate\Support\Str;
use PHPUnit\Framework\Assert;
use Inertia\Testing\AssertableInertia;

public function boot(): void
{
    AssertableInertia::macro('whereSomeContainsString', function (array $keys, string $search) {
        Assert::assertTrue(
            collect($this->prop())->only($keys)->some(fn ($actual) => Str::contains($actual, $search)),
            sprintf('[%s] did not contain [%s].', Arr::join($keys, ', ', $search))
        )
    });
}

あとは、いつもの検証関数みたいに利用するだけです。

$this->get('/accounts?filter[name]=John')
    ->assertInertia(fn (Assert $page) => $page
        ->has('accounts', fn(Assert $page) => $page
            ->each(fn (Assert $page) => $page
                ->whereSomeContainsString(['first_name', 'last_name'], $searchName)
                ->etc()
            )
        )
    );

この方が自然で何を検証しているか分かり易いですね。

まとめ

AssertableInertia で 2 つ以上の値を同時に検証する方法でした。

参考文献

最後に

少しでも参考になればと思います!

Arsaga Developers Blog

Discussion