事の発端
jestやRTSをつかってReactのテストを作成していて、単体のテストで実行時間が1分以上あるテストが存在し、全体のテスト結果が得られるまで時間がかかる、あるいはタイムアウトしてしまい安定性に欠けていました。
問題のコード
セレクトフォームのオプション内容が指定したものかどうかをテストしていました。(実際のコードは公開できないので以下のコードは実質的に同じような状況になるものです)
問題のコード
test('sampleOptionに入っている100件分のオプションがセレクトフォームに登録されているか?', async () => {
render(<SampleSelect />)
… 中略
// sampleOptionはセレクトフォームに登録される100件分のオプションの名前と値が入ったデータ
sampleOption.forEach((data) => {
screen.getByRole('option', { name: data.name })
})
})
原因
RTSのgetby系メソッドは平均的には1ms程度なのですが、計測した結果上記コードの「screen.getByRole(‘option’, { name: data.name })」という部分が、一回に付き900ms(ほぼ1秒)近くかかっていました。結果としてオプションの数×1秒分の時間がかかっていて、これが遅くなる原因でした。
解決法
getByRoleでオプションを毎回検索すると時間がかかることが分かったので、getAllByRoleでオプションをHTMLElement配列で一度全部取得してから、配列内で検索をかけるようにしてみました。
修正コード
test('sampleOptionに入っている100件分のオプションがセレクトフォームに登録されているか?', async () => {
// 100件近くの
render(<SampleSelect />)
… 中略
// 一旦全てのオプションを取得する
const allOption = screen.getAllByRole('option')
sampleOption.forEach((data) => {
// どうやって検索するかは使用しているライブラリなどに応じて変わる
allOption.find((value) => value.textContent === data.name)
})
})
結果として、前のテストが大体80秒近くかかっていたのが、5秒ほどまで減少しました。
まとめ
getBy系のメソッドは、条件を増やしたり、検索対象が多くなると多少重くなります。一回だけであれば微々たるものですが、これをループなどで何回も呼び出すようにすると重くなります。テストコードにまだ慣れていない間はこのようなことがよく起きるので、テスト結果が出るのに時間がかかってしまうとその分デバッグも遅くなります。遅いと感じたらコードを見直してリファクタリングを行うようにすると効率が良くなり、今後書いていくコードの質も上がっていきます。