Vitestの使い方|React Testing Libraryでコンポーネントテストを書く実践ガイド【2026年版】
Vitest(ヴィテスト)は、ビルドツール「Vite」と同じ仕組みで動く高速なJavaScript/TypeScript向けテストフレームワークです。設定がほとんど不要で、Jestとほぼ同じAPIを持つため移行もしやすく、Reactのコンポーネントテストにも広く使われています。
この記事では、インストールと設定から、React Testing Libraryを使ったコンポーネントテストの書き方、ユーザー操作の再現、マッチャー、モック、Jestからの移行までを、実際に動作確認したコードとあわせて解説します。
目次
まとめ
先に要点を整理します。VitestでReactのテストを書くときの全体像は次のとおりです。
- 導入:
vitestと@testing-library/reactなどを入れ、vitest.configでテスト環境をjsdomに設定するだけで始められます。 - 基本:
describe/it(またはtest)でテストを書き、expectとマッチャーで結果を検証します。文法はJestとほぼ同じです。 - コンポーネント:React Testing Libraryの
renderで描画し、screenで要素を取得、user-eventで操作を再現して、toBeInTheDocumentなどのマッチャーで表示を確かめます。 - 非同期・モック:あとから表示される要素は
findBy*で待ち、外部依存はvi.fn()/vi.mock()で差し替えます。 - 実行:
vitestは既定でウォッチモード、CIなどで1回だけ走らせるならvitest runを使います。
以下では、それぞれの手順を具体的なコードで順番に見ていきます。
Vitestとは|Jestとの違い
Vitestは、Viteの設定・変換・モジュール解決をそのまま利用するテストフレームワークです。Viteを使うプロジェクト(React・Vue・Svelte・Nuxtなど)では追加設定がほぼ不要で、TypeScriptやESモジュールも標準でそのまま扱えます。長らく定番だったJestと比べると、設定の手軽さと実行速度、モダンなスタックへの対応のしやすさが特徴です。
| 項目 | Vitest | Jest |
|---|---|---|
| 設定 | Viteと共有・ほぼ不要 | 別途必要 |
| 実行速度 | 高速(Vite/esbuild) | 標準 |
| ESM | ネイティブ対応 | 追加設定が必要 |
| TypeScript | 標準対応 | ts-jest等が必要 |
| API | Jest互換 | — |
| モック | vi.fn / vi.mock | jest.fn / jest.mock |
| ウォッチ | 既定でON | –watch |
APIがJest互換なので、describe・it・expect といった書き方はそのまま使えます。Jestから移行する場合の置き換えポイントは後半でまとめます。Vitestの読み方や脆弱性などの細かい疑問は、記事末のよくある質問で触れています。
Vitestのインストールと設定
ここではReact + Vite + TypeScriptのプロジェクトを前提に、コンポーネントテストまで動かせる構成を作ります。まず必要なパッケージをインストールします。
npm install -D vitest @testing-library/react @testing-library/jest-dom @testing-library/user-event jsdom @vitejs/plugin-react
それぞれの役割は次のとおりです。vitest がテストランナー本体、@testing-library/react がReactコンポーネントの描画・操作、@testing-library/jest-dom が toBeInTheDocument などDOM向けのマッチャー、@testing-library/user-event がユーザー操作の再現、jsdom がNode上の仮想DOM環境です。
次に設定ファイルを用意します。テスト環境を jsdom に、globals: true で describe/expect をインポートなしで使えるようにし、setupFiles でjest-domのマッチャーを読み込みます。
// vitest.config.js
import { defineConfig } from 'vitest/config'
import react from '@vitejs/plugin-react'
export default defineConfig({
plugins: [react()],
test: {
globals: true,
environment: 'jsdom',
setupFiles: './src/test/setup.js',
},
})
// src/test/setup.js
import '@testing-library/jest-dom'
なお package.json の scripts に "test": "vitest" を加えておくと npm test で起動できます。Vitest 4はVite 6以上・Node.js 20以上が必要です。
基本のテストの書き方
まずはコンポーネントではなく、ただの関数をテストして書き方の型をつかみます。describe でテストをグループ化し、it(test でも可)で個々のケースを書き、expect とマッチャーで結果を検証します。
// src/utils/isZero.js
export function isZero(value) {
return value === 0
}
// src/utils/isZero.test.js
import { describe, it, expect } from 'vitest'
import { isZero } from './isZero.js'
describe('isZero', () => {
it('0を渡すとtrueを返す', () => {
expect(isZero(0)).toBe(true)
})
it('0以外を渡すとfalseを返す', () => {
expect(isZero(1)).toBe(false)
})
})
よく使うマッチャーには、厳密等価を見る toBe、オブジェクトや配列の中身を比べる toEqual、例外が投げられることを確かめる toThrow などがあります。例外のテストは、関数を直接呼ぶのではなく「関数を呼ぶ関数」を渡すのがポイントです。
import { describe, it, expect } from 'vitest'
function divide(a, b) {
if (b === 0) throw new Error('0で割れません')
return a / b
}
describe('divide', () => {
it('正しく割り算する', () => {
expect(divide(6, 2)).toBe(3)
})
it('0で割ると例外を投げる', () => {
expect(() => divide(1, 0)).toThrow('0で割れません')
})
})
テストファイルの命名と置き場所
Vitestは、ファイル名が .test.ts/.test.js(または .spec.*)で終わるファイルを自動的にテストとして認識します。拡張子の前は自由なので、対象が isZero.ts ならテストは isZero.test.ts とするのが分かりやすいです。置き場所は、対象ファイルの隣に置く「コロケーション」方式と、__tests__ ディレクトリにまとめる方式のどちらでも構いません。単体テストと結合テストを区別したい場合は、isZero.unit.test.ts のようにレベルを名前に含めておくと、後から実行範囲を絞り込みやすくなります。どの範囲をテスト対象にするかは設定の include/exclude でも調整できます。
テストの実行はコマンド1つです。Vitestは既定でウォッチモードで起動し、ファイルを保存するたびに自動で再実行します。CIなど1回だけ実行したい場面では run を付けます。
npx vitest # ウォッチモードで起動(既定)
npx vitest run # 1回だけ実行(CI向け)
npx vitest run --coverage # カバレッジ計測
npx vitest --ui # ブラウザUIで結果を確認
React Testing Libraryでコンポーネントをテストする
ここからが本題のReactコンポーネントのテストです。VitestとReact Testing Library(RTL)を組み合わせると、「描画して・操作して・表示を確かめる」という実際のユーザー体験に近い形でテストできます。RTLそのものの考え方はReact Testing Libraryとは?ユーザー体験を重視したテストツールの全貌も参考にしてください。
renderとscreenで描画する
テスト対象として、初期値を表示しボタンで数値が増える簡単なカウンターを用意します。render でコンポーネントを描画し、screen から要素を取得して検証します。
// src/components/Counter.jsx
import { useState } from 'react'
export function Counter({ initialCount = 0 }) {
const [count, setCount] = useState(initialCount)
return (
<div>
<p>カウント: {count}</p>
<button onClick={() => setCount((c) => c + 1)}>増やす</button>
</div>
)
}
// src/components/Counter.test.jsx
import { describe, it, expect } from 'vitest'
import { render, screen } from '@testing-library/react'
import { Counter } from './Counter.jsx'
describe('Counter', () => {
it('初期値を表示する', () => {
render(<Counter initialCount={5} />)
expect(screen.getByText('カウント: 5')).toBeInTheDocument()
})
})
要素の取得:getBy / queryBy / findBy
要素の取得方法は大きく3系統あります。挙動の違いを押さえると、テストの書き分けがぐっと楽になります。
| 種類 | 無い時 | 主な用途 |
|---|---|---|
| getBy* | 例外 | 必ず存在する要素 |
| queryBy* | nullを返す | 存在しないことの確認 |
| findBy* | 待機後に例外 | 非同期で出る要素 |
取得の切り口(ByRole・ByText・ByLabelText など)はアクセシビリティを重視した順で選ぶのが推奨です。ボタンなら getByRole('button', { name: '...' }) のように役割と名前で取るのが堅牢です。
user-eventでユーザー操作を再現する
クリックや入力といった操作は @testing-library/user-event で再現します。userEvent.setup() で準備し、操作は await を付けて呼びます。
import { describe, it, expect } from 'vitest'
import { render, screen } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import { Counter } from './Counter.jsx'
describe('Counter', () => {
it('ボタンを押すとカウントが増える', async () => {
const user = userEvent.setup()
render(<Counter />)
await user.click(screen.getByRole('button', { name: '増やす' }))
expect(screen.getByText('カウント: 1')).toBeInTheDocument()
})
})
よく使うマッチャー(toBeInTheDocument / toBeVisible)
jest-domを読み込むと、DOM向けのマッチャーが使えます。要素の存在は toBeInTheDocument、目に見えて表示されているかは toBeVisible、テキスト内容は toHaveTextContent、入力値は toHaveValue で検証します。toBeVisible は display:none などで隠れている要素を「存在はするが見えない」と区別できるのが toBeInTheDocument との違いです。
expect(screen.getByText('カウント: 1')).toBeInTheDocument()
expect(screen.getByRole('button', { name: '増やす' })).toBeVisible()
expect(screen.getByRole('textbox')).toHaveValue('入力値')
非同期で表示される要素を待つ
データ取得後など、あとから表示される要素は findBy* で待ちます。findBy* はPromiseを返すので await で待機します。
import { describe, it, expect } from 'vitest'
import { render, screen } from '@testing-library/react'
import { AsyncMessage } from './AsyncMessage.jsx'
describe('AsyncMessage', () => {
it('非同期で表示されるテキストを待つ', async () => {
render(<AsyncMessage />)
expect(await screen.findByText('読み込み完了')).toBeInTheDocument()
})
})
モック:vi.fn と vi.mock
外部API呼び出しなどテストしづらい依存は、モックに差し替えます。Vitestでは vi.fn() でモック関数を作り、vi.mock() でモジュールごと置き換えられます(Jestの jest.fn/jest.mock に相当)。考え方はJestを使用したモック関数の作成方法と基本的な使い方と共通しています。
import { describe, it, expect, vi } from 'vitest'
import { greet } from './greet.js'
import { fetchName } from './api.js'
vi.mock('./api.js', () => ({
fetchName: vi.fn(),
}))
describe('greet', () => {
it('APIをモックして挨拶文を組み立てる', async () => {
vi.mocked(fetchName).mockResolvedValue('テスト太郎')
const result = await greet()
expect(result).toBe('こんにちは、テスト太郎さん')
expect(fetchName).toHaveBeenCalledTimes(1)
})
})
mockResolvedValue で非同期の戻り値を、toHaveBeenCalledTimes で呼び出し回数を検証できます。テスト間でモックの状態を持ち越したくない場合は、設定の clearMocks や vi.clearAllMocks() を使ってリセットします。
Jestから移行する場合のポイント
APIが互換なので大半のテストはそのまま動きますが、いくつか押さえておきたい違いがあります。
- モックのプレフィックス:
jest.fn→vi.fn、jest.mock→vi.mockへ置き換えます。 - グローバルAPI:Jestは
describeなどが既定で使えますが、Vitestはglobals: trueを設定するか、各ファイルでvitestからインポートします。globalsを無効にしているとTesting Libraryの自動クリーンアップが効かない点に注意します。 - 設定ファイル:
jest.configの内容をvitest.config(またはVite設定のtestフィールド)へ移します。
より高忠実に実ブラウザでテストしたい場合は、Vitestの「ブラウザモード」も選択肢になります。導入手順はVitest Browser Modeの導入・セットアップ方法を徹底解説で詳しく扱っています。
よくある質問(FAQ)
Vitestの読み方は?
「ヴィテスト(Vitest)」と読みます。Vite(ヴィート)に由来し、Viteのテストツールという位置づけです。
VitestとJestはどちらを使うべき?
Viteを使っているプロジェクトや新規プロジェクトでは、設定の手軽さと速度の面でVitestが有力です。既存のJest資産が大きい場合は、APIが互換なので段階的な移行も可能です。
Create React App(CRA)でも使える?
使えますが、CRAはViteベースではないため、Vite(@vitejs/plugin-react)を導入してVitest用の設定を加える必要があります。Vite製のプロジェクトに比べると一手間かかります。なおCRA自体は現在では非推奨となっており、新規プロジェクトではViteなどの利用が推奨されています。
Vitestの脆弱性が心配なときは?
テストランナーは開発依存(devDependency)として使うのが一般的です。依存パッケージの脆弱性情報は npm audit や公式リリースで確認し、直近のセキュリティ修正を含む安定版(2026年6月時点では4.1.8以降の4.1系)への更新を基本としてください。とくにBrowser ModeやUIサーバーをネットワークに公開する場合は注意が必要で、既定のlocalhost運用であれば影響は限定的です。脆弱性の最新の対応状況は公式アドバイザリでの確認をおすすめします。
実ブラウザでテストしたい場合は?
jsdomではなく実際のブラウザで動かす「ブラウザモード」を使います。基本的な使い方はBrowser Modeにおける基本的な使い方と設定項目を網羅的に紹介を参照してください。