generated at
UIコンポーネントのテストパターン
はじめに
ReactVue.jsなどにおけるUIコンポーネントのテストパターンについて

インテグレーションテスト
概要
DOMなどの外部の要素・モジュールとのやり取りなども含めた振る舞いを検証します
基本的にjsdomhappy-domなどが使用されることが多いと思うため、ブラウザーとの互換性を100%保証できるものではないため、必要に応じてE2Eなどの手法と併用するとよいと思います

レンダリング結果のCSSセレクターによる要素の問い合わせ/shallow rendering/コンポーネントの状態の検証や直接的なメソッド呼び出しなどの様々な機能をサポートします
柔軟・高機能ではあるものの、気をつけて利用しないと、実装の詳細に強く依存したホワイトボックステストになりがちなので注意が必要です

UIコンポーネントに対するブラックボックステストを記述する機能を提供します
CSSセレクターによる問い合わせ/コンポーネントの状態の検査やメソッドの呼び出し/shallow renderingといった機能を意図的に提供しません
こういった機能を意図的に排して、アクセシビリティに基づいた要素の問い合わせなどのみを提供することで、信頼性の高いテストを記述できるようにしてくれます
testing-library.spec.js
import { userEvent } from '@testing-library/user-event'; import { render } from '@testing-library/react'; test('SomeComponent', async () => { const { getByRole, queryByRole } = render(<SomeComponent {...props} />); const user = userEvent.setup(); expect(queryByRole('link', { name: 'foo' })).not.toBeInTheDocument(); await user.click(getByRole('button', { name: 'OK' })); expect(getByRole('link', { name: 'foo' })).toBeVisible(); });
Testing Libraryを使う場合、 *ByTestId APIの利用は極力避けるとよいと思います
試したことはないのですが、@storybook/testもよさそうに見えます

ユニットテスト
Reactのコンポーネントは、副作用を含まぬように記述されていれば、 props を受け取って VNode を返す純粋関数と変わりない(pure components)ため、容易にユニットテストが記述できます
ややマイナーですが、ritewayというテストフレームワークではこのアプローチが採用されています
ritewayの思想としては、Container/Presentationalコンポーネントを分離してpure componentsの比率を増やすことで独立してユニットテスト可能なコンポーネントの割り合いを増やし、残った部分をE2Eテストでカバーすることで効果的にテストを行えるようにすることがおすすめされています
Jestなどが提供するSnapshot Testingも同様のアプローチだと思います
※個人的な意見
個人的にはSnapshot Testingにはやや懐疑的 (自分がSnapshot Testingが生まれた背景をあまり理解できていないだけの可能性もある)
理由はSnapshot Testing偽陰性を生みやすい仕組みだと思っていて、個人的にはむやみな乱用は避けたほうがよいと思っています

E2Eテスト
フロントエンド開発においては、ヘッドレスブラウザーを利用して、バックエンドとの疎通なども含めたUI全体の振る舞いをテストされるケースが多いのではないかと思います
CypressPlaywrightなどが人気な印象

これもE2Eテストの一種かと思います
UIのルック・アンド・フィールをテストする上でとても有用な仕組みだと思います
一見、非常に便利な仕組みではあるものの、本質的に偽陰性を生みやすい仕組みでもあると思うため、乱用には注意が必要
振る舞いを細かくテストしたいケースにおいては、インテグレーションテストやCypress/Playwrightなどの方が得意だと思うため、これらの手法とうまく併用するのがよいと思います

どのパターンに従ってテストを書くとよいのか?
基本
ユニットテスト/E2Eテスト/インテグレーションテストなどの様々なパターンがありますが、各手法にはそれぞれ異なるメリットとデメリットがあります
例えば、信頼性の観点からすればE2Eテストが最も優秀ですが、E2Eテストには様々なデメリットもあります
例)
実行コストが高い (実行に時間がかかる、インフラやCIなどのセットアップに手間がかかる)
記述・メンテナンスコストが他のテスト手法よりも高い (場合によっては、メンテナンスができず、負債になってしまう可能性もある)
不安定なテスト(flaky tests)ができやすい
それぞれに得意・不得意があるため、特定のパターンや手法にこだわるよりは、必要に応じてそれぞれのパターンを併用するのがよいと思います

後からテストを導入する場合
もしまだテスト自動化が行われていないプロジェクトに対して後からテストを導入していきたいような場合は、まずはインテグレーションテストやE2Eテストなどのコードベースの広い部分をカバーできる手法から採用していくとよいと思います
この場合、コードベース的にユニットテストが書きやすい構成にはなっていない可能性もあるとは思うため、まずはインテグレーションテストなどの広い部分をカバーできる手法から導入していった方がやりやすいのではないかと思います
インテグレーションテストやE2Eテストなどによってカバーされているコードに対して少しずつリファクタリングを行い、段階的にユニットテストが記述しやすい構成に変えていけると理想的なのではないかと思います

注意点
jest.mock/vi.mockの使用について
これらのAPIを使うことで特定のサードパーティパッケージをスタブすることができ、非常に便利な機能だと思います
ただし、これらのAPIを利用したテストは本質的にホワイトボックステストに陥ってしまい、信頼性が低下してしまいやすいと思います
なので、特別な理由がない限り、極力これらのAPIの使用は避けたほうがよいと思っています (これらのAPIを使わないとテストが書くのが困難な場合など)
具体的には、jest.mock/vi.mockを使わずともテストがかけるよううまくコードを分離・独立させるか、または対象のサードパーティパッケージを利用したコードもカバーするようなインテグレーションテストを記述するのがよいと思います