0.はじめに
現在Claude Codeを利用してチャットアプリを作成しており、
Jestを使ってテストを行っております。
ただClaude Codeが作成したテストコードをちゃんと理解する必要があるなと思い、
本記事を作成しながら学習して理解を深めようと思った次第です。
1.Jestとは
Meta社(旧Facebook社)が開発した、
JavaScriptおよびTypeScriptアプリケーション向けのテストフレームワークです。
豊富な機能を内蔵し、シンプルな構文と設定不要の設計で、
ユニットテストの作成から実行、
コードカバレッジの計測までを容易に行うことができます。
特にReact、Node.js、Vue、Angular、Next.jsなどの
様々なJavaScriptライブラリやフレームワークに対応しており、
高速なテスト実行と詳細なエラーレポートが特徴です。
1.1 Jestの特徴(メリット)
シンプルな設計と導入の容易さ
- 設定がほぼ不要で簡単に利用できる
- 直感的で分かりやすいAPIを提供し、負担なくテストコードを書ける
豊富な機能
-
モック機能:
テスト対象のコードが依存する外部機能やライブラリを模倣し、
テストの実行を独立して行える。 -
スナップショットテスト:
大規模なオブジェクトの出力を記録し、次回以降の出力と自動で比較することで、
予期せぬ変更を検出できます。 -
コードカバレッジ:
テストコードによって、
実際のコードがカバーされているかを計測する機能も内蔵。 -
テストの並列実行:
テストを並列で実行することで、テストの実行時間を短縮し、
開発の効率を高める。
高い互換性
- JavaScriptおよびTypeScriptで書かれたあらゆるコードのテストをサポート
- React、Vue、Angular、Node.js(ExpressやNestなど)といった
様々なライブラリやフレームワークと統合して利用可能
詳細なエラーメッセージ
テストが失敗した際に、問題の原因を明確に示す
詳細なエラーメッセージが表示されるため、問題の特定と解決を容易に行える。
Next.jsでJestを使うメリット
- Next.jsの機能(画像・スタイルのimportなど)を考慮した設定を
next/jest経由で簡単に行える。
※公式ガイドでも Next.js + Jest + React Testing Library の組合せを推奨。
- 組み込みのモック機能(jest.fn()など)で外部依存を切り離してテスト可能
- DOM向けの便利な拡張マッチャー(@testing-library/jest-dom)を併用すると、
読みやすいテストが書ける
自分のイメージですが、
- 単体テスト(ユニットテスト)・結合テスト(インテグレーションテスト):Jest
- 総合テスト(e2eテスト):PlayWright
のイメージです。
2.環境構築
前提:Node.js(LTS推奨)がインストールされていること。
2.1 Next.js環境構築
npx create-next-app@latest my-app --yes
# --yes
# --yes を指定すると、保存された設定またはデフォルト設定を使用して
# プロンプトをスキップします。デフォルトの設定では、
# TypeScript、Tailwind、App Router、Turbopack が有効になり、
# インポートエイリアスは @/* になります。
# Jest本体 + Next.js対応
npm install -D jest jest-environment-jsdom @types/jest ts-jest ts-node
npm install -D @testing-library/react @testing-library/dom @testing-library/jest-dom @testing-library/user-event
// Jest初期設定
npm init jest@latest
It seems that you already have a jest configuration, do you want to override it? … yes
The following questions will help Jest to create a suitable configuration for your project
✔ Would you like to use Typescript for the configuration file? … yes
✔ Choose the test environment that will be used for testing › jsdom (browser-like)
✔ Do you want Jest to add coverage reports? … yes
✔ Which provider should be used to instrument code for coverage? › v8
✔ Automatically clear mock calls, instances, contexts and results before every test? … yes
# 本記事ではデフォルト選択
# 上記選択を終えると、jest.config.tsが作成されます。
2.2 jest.config.ts(Jestの全体的な設定ファイル)
/**
* For a detailed explanation regarding each configuration property, visit:
* https://jestjs.io/docs/configuration
*/
import type { Config } from 'jest';
import nextJest from 'next/jest.js';
// Next.js 用の Jest 設定を生成するヘルパーを作る
const createJestConfig = nextJest({
dir: './', // Next.js プロジェクトのルートパス
})
const config: Config = {
// All imported modules in your tests should be mocked automatically
// automock: false,
// Stop running tests after `n` failures
// bail: 0,
// The directory where Jest should store its cached dependency information
// cacheDirectory: "/private/var/folders/0x/5tg412kd4cg3g7nm02wfhnd00000gn/T/jest_dx",
// Automatically clear mock calls, instances, contexts and results before every test
clearMocks: true,
// Indicates whether the coverage information should be collected while executing the test
collectCoverage: true,
// An array of glob patterns indicating a set of files for which coverage information should be collected
// collectCoverageFrom: undefined,
// The directory where Jest should output its coverage files
coverageDirectory: "coverage",
// An array of regexp pattern strings used to skip coverage collection
// coveragePathIgnorePatterns: [
// "/node_modules/"
// ],
// Indicates which provider should be used to instrument code for coverage
coverageProvider: "v8",
// A list of reporter names that Jest uses when writing coverage reports
// coverageReporters: [
// "json",
// "text",
// "lcov",
// "clover"
// ],
// An object that configures minimum threshold enforcement for coverage results
// coverageThreshold: undefined,
// A path to a custom dependency extractor
// dependencyExtractor: undefined,
// Make calling deprecated APIs throw helpful error messages
// errorOnDeprecated: false,
// The default configuration for fake timers
// fakeTimers: {
// "enableGlobally": false
// },
// Force coverage collection from ignored files using an array of glob patterns
// forceCoverageMatch: [],
// A path to a module which exports an async function that is triggered once before all test suites
// globalSetup: undefined,
// A path to a module which exports an async function that is triggered once after all test suites
// globalTeardown: undefined,
// A set of global variables that need to be available in all test environments
// globals: {},
// The maximum amount of workers used to run your tests. Can be specified as % or a number. E.g. maxWorkers: 10% will use 10% of your CPU amount + 1 as the maximum worker number. maxWorkers: 2 will use a maximum of 2 workers.
// maxWorkers: "50%",
// An array of directory names to be searched recursively up from the requiring module's location
// moduleDirectories: [
// "node_modules"
// ],
// An array of file extensions your modules use
// moduleFileExtensions: [
// "js",
// "mjs",
// "cjs",
// "jsx",
// "ts",
// "mts",
// "cts",
// "tsx",
// "json",
// "node"
// ],
// A map from regular expressions to module names or to arrays of module names that allow to stub out resources with a single module
// moduleNameMapper: {},
// An array of regexp pattern strings, matched against all module paths before considered 'visible' to the module loader
// modulePathIgnorePatterns: [],
// Activates notifications for test results
// notify: false,
// An enum that specifies notification mode. Requires { notify: true }
// notifyMode: "failure-change",
// A preset that is used as a base for Jest's configuration
// preset: undefined,
// Run tests from one or more projects
// projects: undefined,
// Use this configuration option to add custom reporters to Jest
// reporters: undefined,
// Automatically reset mock state before every test
// resetMocks: false,
// Reset the module registry before running each individual test
// resetModules: false,
// A path to a custom resolver
// resolver: undefined,
// Automatically restore mock state and implementation before every test
// restoreMocks: false,
// The root directory that Jest should scan for tests and modules within
// rootDir: undefined,
// A list of paths to directories that Jest should use to search for files in
// roots: [
// "<rootDir>"
// ],
// Allows you to use a custom runner instead of Jest's default test runner
// runner: "jest-runner",
// The paths to modules that run some code to configure or set up the testing environment before each test
// setupFiles: [],
// A list of paths to modules that run some code to configure or set up the testing framework before each test
// 各テスト実行前に読み込むファイルを指定
// ここに書いたファイルの中身は、全テストで自動的に読み込まれる
setupFilesAfterEnv: ['<rootDir>/jest.setup.ts'],
// The number of seconds after which a test is considered as slow and reported as such in the results.
// slowTestThreshold: 5,
// A list of paths to snapshot serializer modules Jest should use for snapshot testing
// snapshotSerializers: [],
// The test environment that will be used for testing
// テストを実行する環境を指定
// jsdom = ブラウザっぽい環境
testEnvironment: "jsdom",
// Options that will be passed to the testEnvironment
// testEnvironmentOptions: {},
// Adds a location field to test results
// testLocationInResults: false,
// The glob patterns Jest uses to detect test files
// testMatch: [
// "**/__tests__/**/*.?([mc])[jt]s?(x)",
// "**/?(*.)+(spec|test).?([mc])[jt]s?(x)"
// ],
// An array of regexp pattern strings that are matched against all test paths, matched tests are skipped
// testPathIgnorePatterns: [
// "/node_modules/"
// ],
// The regexp pattern or array of patterns that Jest uses to detect test files
// testRegex: [],
// This option allows the use of a custom results processor
// testResultsProcessor: undefined,
// This option allows use of a custom test runner
// testRunner: "jest-circus/runner",
// A map from regular expressions to paths to transformers
// transform: undefined,
// An array of regexp pattern strings that are matched against all source file paths, matched files will skip transformation
// transformIgnorePatterns: [
// "/node_modules/",
// "\\.pnp\\.[^\\/]+$"
// ],
// An array of regexp pattern strings that are matched against all modules before the module loader will automatically return a mock for them
// unmockedModulePathPatterns: undefined,
// Indicates whether each individual test should be reported during the run
// verbose: undefined,
// An array of regexp patterns that are matched against all source file paths before re-running tests in watch mode
// watchPathIgnorePatterns: [],
// Whether to use watchman for file crawling
// watchman: true,
};
// 最終的に Jest が読む設定をエクスポート
export default createJestConfig(config);
2.2 jest.setup.ts(Jestの全テスト共通の初期設定ファイル)
// @testing-library/jest-dom を全テストで有効にする
import '@testing-library/jest-dom';
// --- 共通で使う fetch モックを定義 ---
beforeAll(() => {
global.fetch = jest.fn(() =>
Promise.resolve({
ok: true,
json: () => Promise.resolve({ message: 'ok' }),
})
) as jest.Mock
});
afterAll(() => {
// 各テスト後に呼び出し回数や引数をリセット
jest.clearAllMocks();
})
2.3 package.json(Jestのコマンド追加)
"scripts": {
"test": "jest",
"test:watch": "jest --watch",
"test:coverage": "jest --coverage"
}
--watch:開発中に変更を監視しながらテストを自動で再実行
--coverage:どのコードがテストでカバーされているかを確認
3.本実装・テストコード実装
3.1 本実装(サインアップ画面(react-hook-form使用))
"use client"
import { useState } from "react";
import { useForm } from "react-hook-form";
type SignUpFormInputs = {
email: string;
password: string;
}
type Props = {
onSubmit?: (email: string, password: string) => void;
};
export default function SignUpPage({ onSubmit }: Props) {
const [message, setMessage] = useState("");
const { register, handleSubmit, formState: { errors, isSubmitting }, }
= useForm<SignUpFormInputs>()
// フォーム送信時の処理
const submitHandler = async (data: SignUpFormInputs) => {
if (onSubmit) {
// テストから渡されたモック関数を呼び出す
onSubmit(data.email, data.password);
return
}
// API呼び出し(現状の実装だと動かない、テストはFetchモックでカバー)
try {
const res = await fetch('/api/signup', {
method: 'POST',
headers: { "Content-Type": "applications/json" },
body: JSON.stringify(data),
});
const result = await res.json();
if (res.ok) {
setMessage(result.message || "サインアップ成功!");
} else {
setMessage(result.error || "サインアップに失敗しました");
}
} catch (err) {
setMessage(`ネットワークエラーが発生しました:${err}`,)
}
}
return (
<div className="max-w-md mx-auto mt-10 p-6 border rounded">
<h1 className="text-2xl font-bold mb-4">Sign Up</h1>
<form
onSubmit={handleSubmit(submitHandler)}
className="space-y-4"
noValidate
>
{/* メールアドレス */}
<div>
<input
type="email"
placeholder="test@example.com"
className="border p-2 w-full"
{...register("email", {
required: "メールアドレスは必須です",
pattern: {
value: /^[^\s@]+@[^\s@]+\.[^\s@]+$/,
message: "正しいメールアドレスを入力してください",
}
})}
/>
{errors.email && (
<p className="text-red-500 text-sm">{errors.email.message}</p>
)}
</div>
{/* パスワード */}
<div>
<input
type="password"
placeholder="********"
className="border p-2 w-full"
{...register("password", {
required: "パスワードは必須です",
minLength: {
value: 8,
message: "パスワードは8文字以上必要です",
}
})}
/>
{errors.password && (
<p className="text-red-500 text-sm">{errors.password.message}</p>
)}
</div>
<button
type="submit"
disabled={isSubmitting}
className="bg-blue-500 text-white px-4 py-2 rounded"
>
Sign Up
</button>
</form>
{/* 結果メッセージ */}
{message && <p className="mt-4">{message}</p>}
</div>
);
}
3.2 テストコード実装
src/app/__tests__にテストファイルを作成
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event'
import SignUpPage from '../signup/page';
describe('SignUpPage API 呼び出し', () => {
it('サインアップ成功時に成功メッセージを表示する', async () => {
const user = userEvent.setup();
// fetchを成功レスポンスでモック
; (global.fetch as jest.Mock).mockResolvedValueOnce({
ok: true,
json: async () => ({ message: 'ユーザー作成されました!' }),
})
render(<SignUpPage />)
// 入力操作
await user.type(screen.getByPlaceholderText('test@example.com'), 'user@test.com');
await user.type(screen.getByPlaceholderText('********'), 'secret123');
// ボタンクリック
await user.click(screen.getByRole('button', { name: /Sign Up/i }));
// 成功メッセージが表示されることを確認
expect(await screen.findByText("ユーザー作成されました!")).toBeInTheDocument();
});
it('サインアップ失敗時にエラーメッセージを表示する', async () => {
const user = userEvent.setup();
// fetch を失敗レスポンスでモック (res.ok = false)
; (global.fetch as jest.Mock).mockResolvedValueOnce({
ok: false,
json: async () => ({ error: 'サインアップに失敗しました' }),
})
render(<SignUpPage />)
await user.type(screen.getByPlaceholderText('test@example.com'), 'user@test.com');
await user.type(screen.getByPlaceholderText('********'), 'secret123');
await user.click(screen.getByRole('button', { name: /Sign Up/i }));
expect(await screen.findByText('サインアップに失敗しました')).toBeInTheDocument();
});
it('ネットワークエラー発生時にネットワークエラーメッセージを表示する', async () => {
const user = userEvent.setup();
// fetch を例外でモック
; (global.fetch as jest.Mock).mockRejectedValueOnce(new Error('timeout'))
render(<SignUpPage />)
await user.type(screen.getByPlaceholderText('test@example.com'), 'user@test.com');
await user.type(screen.getByPlaceholderText('********'), 'secret123');
await user.click(screen.getByRole('button', { name: /Sign Up/i }));
expect(await screen.findByText(/ネットワークエラーが発生しました/)).toBeInTheDocument();
});
});
describe('SignUpPage onSubmit prop', () => {
it('onSubmit が渡された場合は API 呼び出しを行わず onSubmit を呼ぶ', async () => {
const user = userEvent.setup();
const mockSubmit = jest.fn();
render(<SignUpPage onSubmit={mockSubmit} />);
await user.type(screen.getByPlaceholderText('test@example.com'), 'user@test.com');
await user.type(screen.getByPlaceholderText('********'), 'secret123');
await user.click(screen.getByRole('button', { name: /Sign Up/i }));
expect(mockSubmit).toHaveBeenCalledWith('user@test.com', 'secret123');
});
});
describe('SignUpPage API fallback messages', () => {
it('成功レスポンスに message がない場合はデフォルト成功メッセージを表示する', async () => {
const user = userEvent.setup();
; (global.fetch as jest.Mock).mockResolvedValueOnce({
ok: true,
json: async () => ({}),
})
render(<SignUpPage />)
await user.type(screen.getByPlaceholderText('test@example.com'), 'user@test.com');
await user.type(screen.getByPlaceholderText('********'), 'secret123');
await user.click(screen.getByRole('button', { name: /Sign Up/i }));
expect(await screen.findByText('サインアップ成功!')).toBeInTheDocument();
});
it('失敗レスポンスに error がない場合はデフォルト失敗メッセージを表示する', async () => {
const user = userEvent.setup();
; (global.fetch as jest.Mock).mockResolvedValueOnce({
ok: false,
json: async () => ({}),
})
render(<SignUpPage />)
await user.type(screen.getByPlaceholderText('test@example.com'), 'user@test.com');
await user.type(screen.getByPlaceholderText('********'), 'secret123');
await user.click(screen.getByRole('button', { name: /Sign Up/i }));
expect(await screen.findByText('サインアップに失敗しました')).toBeInTheDocument();
});
});
describe('SignUpPage バリデーション', () => {
it('短すぎるパスワードの場合エラーメッセージを表示する', async () => {
const user = userEvent.setup();
render(<SignUpPage />);
// 入力操作(パスワードが短すぎる)
await user.type(screen.getByPlaceholderText('test@example.com'), 'user@test.com');
await user.type(screen.getByPlaceholderText('********'), '123');
// ボタンクリック
await user.click(screen.getByRole('button', { name: /Sign Up/i }));
// エラーメッセージが表示されることを確認
expect(await screen.findByText("パスワードは8文字以上必要です")).toBeInTheDocument();
});
it('メールアドレス必須エラーメッセージを表示', async () => {
const user = userEvent.setup();
render(<SignUpPage />);
// 入力操作(メールアドレス未入力)
await user.type(screen.getByPlaceholderText('********'), 'secret123');
// ボタンクリック
await user.click(screen.getByRole('button', { name: /Sign Up/i }));
// エラーメッセージが表示されることを確認
expect(await screen.findByText("メールアドレスは必須です")).toBeInTheDocument();
});
it('メールアドレス入力形式エラーメッセージを表示', async () => {
const user = userEvent.setup();
render(<SignUpPage />);
// 入力操作(メールアドレス入力形式不正)
await user.type(screen.getByPlaceholderText('test@example.com'), 'test.com');
await user.type(screen.getByPlaceholderText('********'), 'secret123');
// ボタンクリック
await user.click(screen.getByRole('button', { name: /Sign Up/i }));
// エラーメッセージが表示されることを確認
expect(await screen.findByText("正しいメールアドレスを入力してください")).toBeInTheDocument();
});
it('パスワード必須エラーメッセージを表示', async () => {
const user = userEvent.setup();
render(<SignUpPage />);
// 入力操作(パスワード未入力)
await user.type(screen.getByPlaceholderText('test@example.com'), 'user@test.com');
// ボタンクリック
await user.click(screen.getByRole('button', { name: /Sign Up/i }));
// エラーメッセージが表示されることを確認
expect(await screen.findByText("パスワードは必須です")).toBeInTheDocument();
});
});
3.3 良く使用するマッチャー
// ========================================
// 等価性のテスト
// ========================================
// toBe: プリミティブ値の厳密等価比較(===と同じ)
// 数値、文字列、真偽値、null、undefinedなどの比較に使用
expect(2 + 2).toBe(4);
expect('hello').toBe('hello');
expect(true).toBe(true);
// toEqual: オブジェクトや配列の内容を再帰的に比較
// プロパティの値が同じであれば、異なるオブジェクトインスタンスでもテストパス
expect({name: 'test', age: 25}).toEqual({name: 'test', age: 25});
expect([1, 2, 3]).toEqual([1, 2, 3]);
// toStrictEqual: toEqualよりも厳密な比較
// undefinedプロパティやsparse配列の違いも検出
expect({name: 'test'}).toStrictEqual({name: 'test'});
// ========================================
// 真偽値のテスト
// ========================================
// toBeTruthy: JavaScriptで真と評価される値をテスト
// true, 1, "hello", {}, []などがパス
expect(1).toBeTruthy();
expect('hello').toBeTruthy();
expect({}).toBeTruthy();
// toBeFalsy: JavaScriptで偽と評価される値をテスト
// false, 0, "", null, undefined, NaNがパス
expect(0).toBeFalsy();
expect('').toBeFalsy();
expect(null).toBeFalsy();
// toBeNull: 値がnullかどうかを厳密にテスト
expect(null).toBeNull();
expect(undefined).not.toBeNull();
// toBeUndefined: 値がundefinedかどうかを厳密にテスト
expect(undefined).toBeUndefined();
expect(null).not.toBeUndefined();
// toBeDefined: 値がundefined以外かどうかをテスト
expect('hello').toBeDefined();
expect(0).toBeDefined();
expect(null).toBeDefined();
// ========================================
// 数値のテスト
// ========================================
// toBeGreaterThan: 指定した値より大きいかテスト
expect(10).toBeGreaterThan(5);
// toBeGreaterThanOrEqual: 指定した値以上かテスト
expect(10).toBeGreaterThanOrEqual(10);
// toBeLessThan: 指定した値より小さいかテスト
expect(5).toBeLessThan(10);
// toBeLessThanOrEqual: 指定した値以下かテスト
expect(5).toBeLessThanOrEqual(5);
// toBeCloseTo: 浮動小数点数の近似値比較(第2引数は小数点以下の桁数)
// 浮動小数点の計算誤差を考慮したテストに使用
expect(0.1 + 0.2).toBeCloseTo(0.3, 5);
expect(Math.PI).toBeCloseTo(3.14, 2);
// toBeNaN: 値がNaN(Not a Number)かどうかをテスト
expect(Number('abc')).toBeNaN();
expect(0 / 0).toBeNaN();
// ========================================
// 文字列のテスト
// ========================================
// toMatch: 文字列が正規表現にマッチするかテスト
expect('hello world').toMatch(/world/);
expect('test@example.com').toMatch(/\w+@\w+\.\w+/);
// toContain: 文字列が指定した部分文字列を含むかテスト
expect('hello world').toContain('world');
expect('JavaScript').toContain('Script');
// toHaveLength: 文字列の長さをテスト
expect('hello').toHaveLength(5);
// toStartWith: 文字列が指定した文字列で始まるかテスト(カスタムマッチャー)
// toEndWith: 文字列が指定した文字列で終わるかテスト(カスタムマッチャー)
// ========================================
// 配列・反復可能オブジェクトのテスト
// ========================================
// toContain: 配列が指定した要素を含むかテスト
expect(['apple', 'banana', 'orange']).toContain('apple');
expect([1, 2, 3, 4, 5]).toContain(3);
// toHaveLength: 配列やオブジェクトの長さ・要素数をテスト
expect(['a', 'b', 'c']).toHaveLength(3);
expect('hello').toHaveLength(5);
// toContainEqual: 配列が指定したオブジェクトと等価な要素を含むかテスト
expect([{id: 1}, {id: 2}]).toContainEqual({id: 1});
// arrayContaining: 配列が指定した要素を部分的に含むかテスト
expect(['a', 'b', 'c']).toEqual(expect.arrayContaining(['a', 'c']));
// ========================================
// オブジェクトのテスト
// ========================================
// toHaveProperty: オブジェクトが指定したプロパティを持つかテスト
expect({name: 'test', age: 25}).toHaveProperty('name');
expect({user: {name: 'test'}}).toHaveProperty('user.name', 'test');
// toMatchObject: オブジェクトが指定したプロパティとその値を持つかテスト
// 完全一致ではなく、指定したプロパティのみをチェック
expect({name: 'test', age: 25, city: 'Tokyo'}).toMatchObject({name: 'test', age: 25});
// objectContaining: オブジェクトが指定したプロパティを部分的に含むかテスト
expect({name: 'test', age: 25}).toEqual(expect.objectContaining({name: 'test'}));
// ========================================
// 例外(エラー)のテスト
// ========================================
// toThrow: 関数が例外を投げるかテスト(例外メッセージの指定なし)
expect(() => {
throw new Error('Something went wrong');
}).toThrow();
// toThrow: 特定のエラーメッセージを投げるかテスト
expect(() => {
throw new Error('ファイルが見つかりません');
}).toThrow('ファイルが見つかりません');
// toThrow: 正規表現でエラーメッセージをテスト
expect(() => {
throw new Error('Error: Invalid input');
}).toThrow(/Invalid/);
// toThrow: 特定のエラークラスを投げるかテスト
expect(() => {
throw new TypeError('型エラー');
}).toThrow(TypeError);
// ========================================
// 非同期処理のテスト
// ========================================
// resolves: Promiseが正常に解決される値をテスト
await expect(Promise.resolve('成功')).resolves.toBe('成功');
await expect(fetchUserData(1)).resolves.toHaveProperty('name');
// rejects: Promiseが拒否される値をテスト
await expect(Promise.reject('エラー')).rejects.toMatch('エラー');
await expect(fetchUserData(-1)).rejects.toThrow('無効なユーザーID');
// ========================================
// 関数・モックのテスト
// ========================================
// toHaveBeenCalled: モック関数が呼び出されたかテスト
const mockFn = jest.fn();
mockFn();
expect(mockFn).toHaveBeenCalled();
// toHaveBeenCalledTimes: モック関数が指定回数呼び出されたかテスト
const mockFn2 = jest.fn();
mockFn2();
mockFn2();
expect(mockFn2).toHaveBeenCalledTimes(2);
// toHaveBeenCalledWith: モック関数が指定した引数で呼び出されたかテスト
const mockFn3 = jest.fn();
mockFn3('test', 123);
expect(mockFn3).toHaveBeenCalledWith('test', 123);
// toHaveBeenLastCalledWith: モック関数の最後の呼び出しの引数をテスト
const mockFn4 = jest.fn();
mockFn4('first');
mockFn4('last');
expect(mockFn4).toHaveBeenLastCalledWith('last');
// toHaveBeenNthCalledWith: モック関数のN回目の呼び出しの引数をテスト
const mockFn5 = jest.fn();
mockFn5('first');
mockFn5('second');
expect(mockFn5).toHaveBeenNthCalledWith(1, 'first');
expect(mockFn5).toHaveBeenNthCalledWith(2, 'second');
// toHaveReturnedWith: モック関数が指定した値を返したかテスト
const mockFn6 = jest.fn(() => 'result');
mockFn6();
expect(mockFn6).toHaveReturnedWith('result');
// ========================================
// インスタンス・型のテスト
// ========================================
// toBeInstanceOf: 値が指定したクラスのインスタンスかテスト
expect(new Date()).toBeInstanceOf(Date);
expect([]).toBeInstanceOf(Array);
expect({}).toBeInstanceOf(Object);
// ========================================
// 否定のテスト
// ========================================
// not: 任意のマッチャーの結果を否定
expect(2 + 2).not.toBe(5); // 4は5ではない
expect('hello').not.toContain('xyz'); // "hello"は"xyz"を含まない
expect([1, 2, 3]).not.toHaveLength(5); // 配列の長さは5ではない
// ========================================
// 実践的な使用例
// ========================================
// 複合的なオブジェクトのテスト例
const user = {
id: 1,
name: '田中太郎',
email: 'tanaka@example.com',
profile: {
age: 30,
city: '東京'
},
hobbies: ['読書', '映画鑑賞']
};
expect(user).toHaveProperty('id', 1); // IDが1
expect(user).toHaveProperty('profile.age', 30); // ネストしたプロパティ
expect(user.hobbies).toContain('読書'); // 配列に特定要素
expect(user.hobbies).toHaveLength(2); // 配列の長さ
expect(user).toMatchObject({name: '田中太郎', email: expect.stringContaining('@')}); // 部分マッチ
// API レスポンスのテスト例
const apiResponse = {
status: 200,
data: {
users: [
{id: 1, name: 'User1'},
{id: 2, name: 'User2'}
]
},
timestamp: new Date()
};
expect(apiResponse.status).toBe(200); // ステータスコード
expect(apiResponse.data.users).toHaveLength(2); // ユーザー数
expect(apiResponse.data.users[0]).toHaveProperty('id'); // 各ユーザーにIDあり
expect(apiResponse.timestamp).toBeInstanceOf(Date); // タイムスタンプが日付オブジェクト
4.Jest実行
npm run test
PASS src/app/__tests__/signup.test.tsx
-----------|---------|----------|---------|---------|-------------------
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
-----------|---------|----------|---------|---------|-------------------
All files | 100 | 100 | 100 | 100 |
signup | 100 | 100 | 100 | 100 |
page.tsx | 100 | 100 | 100 | 100 |
-----------|---------|----------|---------|---------|-------------------
Test Suites: 1 passed, 1 total
Tests: 11 passed, 11 total
Snapshots: 0 total
Time: 1.47 s
Ran all test suites.
5.まとめ
本記事を作成した中で、Jestの理解が少し深まりました。
Claude Codeの実装内容を把握出来る状態になって、
まずは上記のよく使うマッチャーの理解を深めていきたいと思います。