こんにちは!フロントエンドの現場を渡り歩いてきた、チーフアーキテクトの私です。
Reactを学び始めて、`useState`で画面の数字が変わったり、入力した文字が反映されたりするようになったときって、なんだか魔法みたいでワクワクしますよね。「おっ、私、動的なWebアプリを作ってるぞ!」という実感が湧いてくる最高の瞬間です。
でも、しばらくしてこんな壁にぶつかりませんでしたか?
「あれ? 自分でボタンを押して動かしたときはバッチリ動くのに、これ、本当にちゃんと動き続ける保証はあるの…?」
「アプリが大きくなったとき、コードを書き換えても壊れてないってどうやって確かめればいいんだろう…」
そう、手動での動作確認(ブラウザをポチポチクリックする作業)には限界があります。コードを直すたびに全機能を確認するのは、終わりのない終わらない持久走のようでヘトヘトになりますよね。
そこで登場するのが、React Testing Library(RTL)です!
今回は、`useState`を使ったコンポーネントが、ちゃんと期待通りに動くかをロボット(テスト)にお任せして確かめる方法を、身近な例えを交えながら優しく紐解いていきましょう。
大丈夫、最初は誰もがチンプンカンプンになります。一つずつ丁寧に見ていきましょうね。
—
そもそも、テストってなんだろう?(お買い物レシートの例え)
テストコードを書くというと、「なんだか難しそうだな…バグを見つけるための厳しい監査役みたいなものかな?」身構えてしまうかもしれません。
でも、もっと気楽に考えてみてください。これは「自動でお買い物の計算をしてくれるレシート」のようなものです。
スーパーでお買い物カゴに商品をポンポン入れて、レジに通したとき、レシートには「合計金額」と「お釣りの計算」が正しく印刷されて出てきますよね。もしレジの機械が壊れていて、100円のパンを買ったのに1万円と表示されたら大変です。
Reactのテストもこれと全く同じ。
- お買い物カゴに入れる作業 = ユーザーがボタンを押したり、文字を入力したりする操作
- レシートに表示される結果 = 画面に表示されるテキストや数字の変化
「ボタンを1回押したら、数字が『0』から『1』に増える」という一連の流れが正しく行われているかを、テスト用の小さなロボットがパパッと確認してくれるのです。
—
今回テストするコンポーネントを見てみよう
まずは、今回テストのターゲットにする「カウンターアプリ」のコードを見てみましょう。よく見るお馴染みのやつですね。
import React, { useState } from ‘react’;
export function Counter() {
// 状態(カウント)を保持する。初期値は 0
const [count, setCount] = useState(0);
return (
現在のカウント: {count}
{/ 押すとカウントが1増えるボタン /}
);
}
「ボタンを押すと `count` が増えて、画面の表示が変わる」という、非常にシンプルな仕組みです。
このコンポーネントに対して、「本当にボタンを押したら数字が増えるのか?」をテストするコードを書いてみましょう。
—
テストコードを書いてみよう(実用的なサンプル)
テストを書くファイル(例えば `Counter.test.jsx` など)に、以下のように記述します。
エディタにそのまま貼り付けて動かせるよう、日本語のコメントをたっぷり入れておきました。
import React from ‘react’;
import { render, screen } from ‘@testing-library/react’;
import userEvent from ‘@testing-library/user-event’;
import { Counter } from ‘./Counter’; // さっきのコンポーネントをインポート
// テストのグループ(テストスイート)を作ります
describe(‘Counterコンポーネントのテスト’, () => {
// テストケース1:初期表示の確認
test(‘最初はカウントが「0」からスタートすること’, () => {
// 1. コンポーネントを仮想的な画面に描画(レンダリング)する
render(
// 2. 「現在のカウント: 0」という文字が画面にあるか探して確認する
const countDisplay = screen.getByText(/現在のカウント: 0/i);
expect(countDisplay).toBeInTheDocument();
});
// テストケース2:ユーザー操作(ボタンクリック)の確認
test(‘ボタンを1回押すと、カウントが「1」に増えること’, async () => {
// ユーザーの操作(クリックなど)をリアルに再現するための準備
const user = userEvent.setup();
// 1. 画面にコンポーネントを描画する
render(
// 2. 「カウントを増やす」という名前のボタンを探す
const button = screen.getByRole(‘button’, { name: ‘カウントを増やす’ });
// 3. 【ユーザー操作のシミュレート】実際にボタンをクリックする!
await user.click(button);
// 4. カウントが「1」に変わっているかを画面から探して確認する
const countDisplay = screen.getByText(/現在のカウント: 1/i);
expect(countDisplay).toBeInTheDocument();
});
});
—
初学者がつまずきやすいポイントと、優しく解決するコツ
ここまで読んだ方の中で、「あれ? なんか色々インポートしてるぞ」「`async/await`って何?」と思った方がいるかもしれません。現場のプロでも最初は戸惑う、つまずきやすいポイントをそっと解説しますね。
1. `screen` ってなに?
`screen` は、いわば「テスト用の目」です。
`render(
2. なぜ `userEvent.click(button)` の前に `await` が必要なの?
ここが今回のテーマである「状態管理の非同期バグ対策」やテストの挙動に直結する非常に大切なポイントです!
Reactの世界では、ボタンが押されて `useState` の更新関数(`setCount`)が走ったとき、画面の再描画(レンダリング)はほんの少しだけ「次の瞬間」に起こります(非同期的なバッチ処理というやつですね)。
`await user.click(button)` と書くことで、「ボタンをクリックして、Reactが内部で状態を更新し、画面の数字を書き換えるまでの処理がすべて終わるのを、ロボットがちゃんと待ってあげる」ことができます。これがあるおかげで、「まだ画面が書き換わってないのにテスト結果を見に行ってエラーになった!」という悲しいすれ違いを防げるのです。
—
チーフアーキテクトからのエール
お疲れ様でした!ここまで読み進めてくれたあなたは、もうただの「動くコードを書く人」から、一歩進んで「品質に責任を持つエンジニア」への階段を登り始めています。
テストコードは、最初はちょっと面倒くさく感じるかもしれません。「わざわざこんなコード書かなくても、ブラウザでポチポチすればいいじゃん」って思う日もあります。
でも、アプリが大きくなって、半年前に入れた機能を直したとき、この自動テストたちが「おいおい、そこのコードを直したら、他の機能が壊れちゃったぜ!」と、汗をかきながらあなたの代わりに教えてくれるようになります。それはもう、心強い相棒のような存在です。
最初は完璧を目指さなくて大丈夫。まずはボタンが1つ動くテストから、気楽に書いてみてくださいね。あなたのコードライフが、もっと楽しく、もっと安心なものになりますように!

コメント