LaravelでInertia.jsを使ったテストの時にAssertを拡張して同時に2つの値を検証する方法
はじめに
Laravel と Inertia.js を使って開発をしている Web エンジニアです。最近、検索機能のテストを行う必要があったのですが、JSON から 2 つの値を同時に検証したり、or 条件で検証することができないという問題が発生しました。そこで、私が見つけたいくつかの解決方法について、この記事で紹介したいと思います。
前提条件
アカウントがありfirst_name
とlast_name
をたとえばJohn
で検索するときにサーバーではfirst_name
とlast_name
をfull_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
を一回で検証する方法です。
以下のようにaccounts
をmap()
でfirst_name
とlast_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_name
とlast_name
を結合してfull_name
にしてから検証することです。この時に AssertableInertia クラスを覗くとtoArray()
を持っていることがわかるのでそこからprops.first_name
とprops.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
を継承していることがわかります。
AssertableJson
はMacroable
を 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 つ以上の値を同時に検証する方法でした。
参考文献
- Laravel Macroable: Understanding macros and mixin
- Fluent JSON Testing
- AssertableInertia
- AssertableJson
最後に
少しでも参考になればと思います!
Discussion