Jest + React Testing Library の使い方
何を組み合わせて使うのか?
フロントエンドのテストでよく出てくるのはこの3つです:
- Jest
⇒ テストランナー(test(...)・expect(...)を提供するやつ) - React Testing Library(@testing-library/react)
⇒ Reactコンポーネントをテスト用のDOMにレンダリングし、画面上の要素を「ユーザー目線」で触るためのユーティリティ - @testing-library/jest-dom
⇒toBeInTheDocumentやtoHaveTextContentなど、DOM向けの便利なアサーションを追加
最近は Vite + Vitest を使う構成も多いですが、
「Jest か Vitest か」はテストランナーの違いであって、RTLの使い方はほぼ同じです。
以降は Jest を前提に書きますが、「テストコードの形」を見る、くらいの感覚でOKです。
シンプルなコンポーネントのテスト例
テスト対象コンポーネント
// Counter.jsx
import { useState } from 'react'
export default function Counter() {
const [count, setCount] = useState(0)
return (
<div>
<p>カウント: {count}</p>
<button onClick={() => setCount((c) => c + 1)}>+1</button>
</div>
)
}テストコード(Jest + RTL)
// Counter.test.jsx
import { render, screen } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import Counter from './Counter'
test('ボタンを押すとカウントが増える', async () => {
const user = userEvent.setup()
// コンポーネントをテスト用DOMにレンダリング
render(<Counter />)
// 画面から要素を「ユーザー目線」で探す
const button = screen.getByRole('button', { name: '+1' })
const text = screen.getByText(/カウント:/)
// 初期状態を確認
expect(text).toHaveTextContent('カウント: 0')
// ボタンクリック
await user.click(button)
// 結果の検証
expect(text).toHaveTextContent('カウント: 1')
})ここで大事なのは:
render(<Counter />)
⇒ コンポーネントを「テスト用の仮想DOM」に描画するscreen.getByRole('button', { name: '+1' })
⇒ 「ロール(button) + ラベル(+1)」で要素を探す
⇒ 実際のユーザーが見るラベルを使って探すという思想userEvent.setup()⇒user.click(...)
⇒ 生のfireEventより「実際のユーザー操作に近い」イベントを発生させるexpect(text).toHaveTextContent('...')
⇒ DOMに対するアサーション(jest-domが追加している)
stateを直接触るのではなく、「DOMにどう見えているか」を検証するのが RTL の流儀です。
非同期処理のテスト
API取得など「時間が掛かる処理」が入ったコンポーネントは、findBy 系の関数で「出てくるまで待つ」書き方をします。
const item = await screen.findByText('取得したタイトル')
expect(item).toBeInTheDocument()また、fetch や API ラッパ関数は Jest の jest.fn() でモックしておき、
- 成功パターン
- 失敗パターン
をそれぞれテストすることができます。
「ユニットテスト」と「コンポーネントテスト」の違い
ユニットテスト(Unit Test)
対象:
- 「小さな単位のロジック」
- 単純な関数
- カスタムフック
- reducer
例:純粋関数
// calc.ts
export function calcTotal(price: number, taxRate: number) {
return Math.round(price * (1 + taxRate))
}// calc.test.ts
import { calcTotal } from './calc'
test('税込価格が正しく計算される', () => {
expect(calcTotal(1000, 0.1)).toBe(1100)
})例:reducer
// counterReducer.ts
export function counterReducer(state, action) {
switch (action.type) {
case 'increment':
return state + 1
default:
return state
}
}test('increment actionで1増える', () => {
expect(counterReducer(0, { type: 'increment' })).toBe(1)
})特徴:
- DOMにレンダリングしない
- 完全にロジックだけをテストする
- テストが速く・壊れにくい
コンポーネントテスト(Component Test)
対象:
- Reactコンポーネント(+その周辺)
例:
さきほどの Counter、LoginForm、PostList など。
render(<Component />)して- 「ある操作をしたら画面がこうなる」を検証する
特徴:
- ユーザー操作と画面表示の一連の流れを確認できる
- props 経由の連携、Context の値など、「コンポーネントの結合」も含めてテストできる
- 内部実装を知らなくてもテストを書ける
どちらをどう使い分ける?
現実的な指針としては:
- ビジネスロジック(計算・変換・条件分岐)
⇒ ユニットテストでガッチリ固める - UIの振る舞い(クリック⇒表示変化、フォーム⇒バリデーション表示)
⇒ コンポーネントテストで確認する
どちらか片方だけ、というよりは「大事なロジックはユニットで固めつつ、主要な画面はコンポーネントテストでユーザー目線チェック」というバランスが現場でも多いです。
React DevTools を使った状態確認・レンダリング確認
テストは「壊れないようにする」仕組みですが、デバッグ中に中身を直接覗くためのツールが React DevTools です。
React DevTools の基本
Chrome / Edge の拡張機能で入れると、DevTools にタブが増えます:
- Components タブ
⇒ コンポーネントツリー、props・state・hooksの中身を見られる - Profiler タブ
⇒ 「どのコンポーネントが何回レンダリングされたか」を記録して分析
Components タブの使い方
- DevTools を開く(F12 or Ctrl+Shift+I)
Componentsタブを開く- 左側にコンポーネントツリーが表示されるので、調べたいコンポーネントをクリック
- 右側に
- props
- state
- hooks(
useState,useReducer,useContextなど)
の中身が表示される
ここでできること:
- 「このコンポーネントが今どんな props を受け取っているか」
- 「state がどんな値になっているか」
- 「Context から何が渡ってきているか」
を「その時点の実物」で確認できます。
ポイント:console.log でstateを出すより、React DevToolsで直接見るほうが「今の最新の値」が確実に分かるので、バグ調査が楽になります。
Profiler タブでレンダリング確認
パフォーマンス系のデバッグをしたいときは Profiler を使います。
Profilerタブを開く- 「Start profiling」ボタンを押して記録開始
- アプリを操作する(スクロール・クリックなど)
- 「Stop profiling」で記録終了
- どのコンポーネントが何回レンダリングされたか、どれくらい時間が掛かったかを見る
ここで、
- 不要に何度もレンダリングされているコンポーネント
- 明らかに重いレンダリングをしているコンポーネント
を発見して、「React.memo や useMemo / useCallback を入れる目星をつける」という使い方ができます。
コンソールログだけに頼らないデバッグのやり方
もちろん console.log も有用ですが、
それだけに頼ると 「ログまみれ/どこで何が起きているか分からない」 状態になりがちです。
他の手段も組み合わせるとだいぶ楽になります。
ブラウザ DevTools のブレークポイント
- Chrome DevTools の
Sourcesタブを開く - 該当する
.jsx/.tsxファイルを選択 - 行番号をクリックしてブレークポイントを置く
- そのコードが実行されると、一時停止して変数の中身をじっくり見られる
ブレークポイントの利点:
console.logのように出力を仕込まなくても、中身が見える- ステップ実行(1行ずつ進める)で、「どこで値が変わったか」を追える
複雑なバグは、最初からブレークポイントで追ったほうが早いことも多いです。
ネットワークタブでAPIを確認
API周りの不具合は、JSコードを見る前に Networkタブ を見た方が早いことが多いです。
- リクエストURLが間違っていないか
- ステータスコード(4xx / 5xx)が返っていないか
- レスポンスのJSONは想定どおりか
Networkタブを見て「サーバーからそもそも間違ったものが来ている」なら、フロントではなくバックエンド側の問題、という切り分けもできます。
コンソールのエラー・Warning をちゃんと読む
Reactは何かおかしいとき、コンソールにかなり親切なメッセージを出してくれます。
- keyがないリスト
- StrictModeによるダブルレンダリング
- 非推奨のAPIの使用
- メモ化ミス
など
まずはコンソールに出ている赤いエラーや黄色いWarningをちゃんと読むこと。
それだけで7割くらいはヒントが見つかります。
小さい単位に分割して再現する
バグが見つからないときの鉄板テクニックは、「問題が起きている部分を最小限のコードに切り出して再現する」ことです。
例えば:
- 大量のコンポーネント+Context+APIが絡んでいる画面でバグが出る
- その部分だけを取り出して、別の小さなコンポーネントとして再実装してみる
これをやることで、「本当にバグの原因になっているコード」、「その周辺で関係ないノイズになっているコード」を切り分けやすくなります。
結果的に、「最小再現コード」を作るプロセスそのものが、一番のデバッグになっていることもよくあります。
テストを「デバッグの道具」として使う
テストは「品質保証」のためだけではなく、「バグの再現・理解のためのツール」としても使えます。
- 再現条件をテストとして書く
- そのテストが落ちたまま、
console.logやブレークポイントでじっくり追う - 修正後、テストが通ることを確認し、同じバグを出さない保険にする
こうしておけば、「前にやらかしたバグ」を二度と踏まないで済みます。
まとめ
- Jest + React Testing Library では
render/screen.getBy*/userEvent/expectを使って- 「ユーザー目線で画面がどう変わるか」をテストする
- ユニットテスト vs コンポーネントテスト
- ユニット:関数・reducer・カスタムフックなどロジック単体
- コンポーネント:DOMにレンダリングしてUIの振る舞いを検証
- React DevTools
- Components タブ:props / state / hooks をリアルタイムに確認
- Profiler タブ:どのコンポーネントが何回レンダリングされているか分析
- デバッグは
- ブレークポイント
- Networkタブ
- コンソールのエラー・Warning
- 小さな再現コード
- テストコードそのもの
を組み合わせて、console.logだけに頼らないスタイルにしていくと楽になる
コメント