Jest で例外処理を検証するテストコードの書き方

開発者にとって、アプリケーションが正常に動作することと同じくらい重要なのは、エラーが発生した際に適切に処理されることです。予期しない入力、ネットワークエラー、システム障害など、様々な例外状況への対応は、堅牢なアプリケーションを構築する上で欠かせません。
この記事では、Jest を使用して例外処理を検証するテストコードの書き方を、基本から実践まで段階的に解説いたします。エラーハンドリングのテストをマスターし、信頼性の高いアプリケーションを構築できるようになりましょう。
例外処理テストの基本概念
例外処理テストとは何か
例外処理テストは、アプリケーションが異常な状況に遭遇した際の動作を検証するテストです。正常系のテストと同様に、例外系のテストも品質保証の重要な要素となります。
なぜ例外処理のテストが重要なのか
現実のアプリケーションでは、以下のような例外状況が頻繁に発生します:
# | 例外のタイプ | 具体例 |
---|---|---|
1 | 入力値エラー | null、undefined、型不一致 |
2 | ビジネスロジックエラー | 計算エラー、状態不整合 |
3 | 外部依存エラー | API 通信エラー、データベース障害 |
4 | システムエラー | メモリ不足、ファイル I/O エラー |
5 | セキュリティエラー | 認証失敗、不正アクセス |
これらの例外状況をテストで検証することで、ユーザーに適切なエラーメッセージを提供し、システムの安定性を保つことができます。
テストすべき例外のタイプ
効果的な例外処理テストを作成するために、テスト対象となる例外を体系的に分類しましょう。
1. 入力値検証エラー
javascript// 不正な入力値をテストする関数例
function calculateAge(birthYear) {
if (typeof birthYear !== 'number') {
throw new TypeError('生年は数値で入力してください');
}
if (
birthYear < 1900 ||
birthYear > new Date().getFullYear()
) {
throw new RangeError(
'生年は1900年から現在年までの範囲で入力してください'
);
}
return new Date().getFullYear() - birthYear;
}
2. ビジネスロジックエラー
javascript// ビジネス制約違反をテストする例
class BankAccount {
constructor(balance = 0) {
this.balance = balance;
}
withdraw(amount) {
if (amount <= 0) {
throw new Error('出金額は正の数である必要があります');
}
if (amount > this.balance) {
throw new Error('残高不足です');
}
this.balance -= amount;
return this.balance;
}
}
3. 非同期処理エラー
javascript// Promise の reject や async/await のエラー
async function fetchUserData(userId) {
const response = await fetch(`/api/users/${userId}`);
if (!response.ok) {
throw new Error(
`ユーザー取得エラー: ${response.status}`
);
}
return response.json();
}
基本的な例外検証パターン
toThrow() マッチャーの使い方
Jest で例外処理をテストする基本的な方法は、toThrow()
マッチャーを使用することです。
基本的な構文
javascriptdescribe('toThrow() マッチャーの基本', () => {
test('例外が発生することを検証', () => {
const throwError = () => {
throw new Error('テストエラー');
};
// 例外が発生することを期待
expect(throwError).toThrow();
});
test('例外が発生しないことを検証', () => {
const safeFunction = () => {
return 'no error';
};
// 例外が発生しないことを期待
expect(safeFunction).not.toThrow();
});
});
よくあるエラーと対処法
以下のようなエラーが発生する場合があります:
vbnetExpected the function to throw an error.
But it didn't throw anything.
Received function did not throw
このエラーは、期待した例外が発生しなかった場合に表示されます。
javascript// 問題のあるテストコード
test('正しくない例外テスト', () => {
const result = throwError(); // 関数を実行してしまっている
expect(result).toThrow(); // これは失敗する
});
// 正しいテストコード
test('正しい例外テスト', () => {
expect(() => throwError()).toThrow(); // 関数リファレンスを渡す
});
具体的なエラーメッセージの検証
単に例外が発生することを確認するだけでなく、具体的なエラーメッセージを検証することで、より詳細なテストができます。
エラーメッセージの完全一致
javascriptdescribe('エラーメッセージの検証', () => {
function validateEmail(email) {
if (!email) {
throw new Error('メールアドレスは必須です');
}
if (!email.includes('@')) {
throw new Error(
'有効なメールアドレスを入力してください'
);
}
return true;
}
test('必須チェックエラーメッセージ', () => {
expect(() => validateEmail()).toThrow(
'メールアドレスは必須です'
);
});
test('フォーマットエラーメッセージ', () => {
expect(() => validateEmail('invalid-email')).toThrow(
'有効なメールアドレスを入力してください'
);
});
});
正規表現によるパターンマッチ
javascriptdescribe('正規表現による例外検証', () => {
function processOrder(orderId) {
if (!orderId) {
throw new Error(
'注文ID ORD-12345 が無効です: 必須パラメータが不足'
);
}
}
test('エラーメッセージのパターンマッチ', () => {
// 部分的なマッチ
expect(() => processOrder()).toThrow(
/注文ID.*が無効です/
);
// 大文字小文字を無視
expect(() => processOrder()).toThrow(/必須パラメータ/i);
// 複数のパターン
expect(() => processOrder()).toThrow(/ORD-\d+/);
});
});
複雑なエラーメッセージの検証
javascriptdescribe('複雑なエラーメッセージ検証', () => {
function calculateDiscount(price, discountRate) {
if (discountRate < 0 || discountRate > 1) {
throw new Error(
`割引率は0から1の範囲で指定してください。入力値: ${discountRate}`
);
}
}
test('動的なエラーメッセージの検証', () => {
// 具体的な値を含むメッセージ
expect(() => calculateDiscount(1000, 1.5)).toThrow(
'割引率は0から1の範囲で指定してください。入力値: 1.5'
);
// stringContaining マッチャーの使用
expect(() => calculateDiscount(1000, -0.2)).toThrow(
expect.stringContaining('入力値: -0.2')
);
});
});
エラータイプの検証
JavaScript では、様々なタイプのエラーオブジェクトが存在します。それぞれを適切に検証することで、より精密なテストができます。
標準エラータイプの検証
javascriptdescribe('エラータイプの検証', () => {
function parseNumber(value) {
if (value === null || value === undefined) {
throw new TypeError(
'値が null または undefined です'
);
}
const num = Number(value);
if (isNaN(num)) {
throw new RangeError('数値に変換できません');
}
return num;
}
test('TypeError の検証', () => {
expect(() => parseNumber(null)).toThrow(TypeError);
expect(() => parseNumber(undefined)).toThrow(TypeError);
// メッセージとタイプの両方を検証
expect(() => parseNumber(null)).toThrow(
new TypeError('値が null または undefined です')
);
});
test('RangeError の検証', () => {
expect(() => parseNumber('abc')).toThrow(RangeError);
expect(() => parseNumber({})).toThrow(RangeError);
});
});
カスタムエラークラスの検証
javascript// カスタムエラークラスの定義
class ValidationError extends Error {
constructor(field, value, message) {
super(message);
this.name = 'ValidationError';
this.field = field;
this.value = value;
}
}
class BusinessError extends Error {
constructor(code, message) {
super(message);
this.name = 'BusinessError';
this.code = code;
}
}
describe('カスタムエラーの検証', () => {
function validateUser(userData) {
if (!userData.email) {
throw new ValidationError(
'email',
userData.email,
'メールアドレスは必須です'
);
}
if (userData.age < 0) {
throw new BusinessError(
'INVALID_AGE',
'年齢は0以上である必要があります'
);
}
}
test('ValidationError の検証', () => {
const invalidUser = { email: '', age: 25 };
expect(() => validateUser(invalidUser)).toThrow(
ValidationError
);
// エラープロパティの検証
try {
validateUser(invalidUser);
} catch (error) {
expect(error).toBeInstanceOf(ValidationError);
expect(error.field).toBe('email');
expect(error.value).toBe('');
expect(error.message).toBe(
'メールアドレスは必須です'
);
}
});
test('BusinessError の検証', () => {
const invalidUser = {
email: 'test@example.com',
age: -5,
};
expect(() => validateUser(invalidUser)).toThrow(
BusinessError
);
// エラーコードの検証
try {
validateUser(invalidUser);
} catch (error) {
expect(error.code).toBe('INVALID_AGE');
expect(error.message).toBe(
'年齢は0以上である必要があります'
);
}
});
});
expect.objectContaining による部分検証
javascriptdescribe('エラーオブジェクトの部分検証', () => {
function createOrder(orderData) {
if (!orderData.customerId) {
const error = new Error('顧客IDが必要です');
error.code = 'MISSING_CUSTOMER_ID';
error.timestamp = new Date().toISOString();
error.details = { received: orderData };
throw error;
}
}
test('エラーオブジェクトの部分的な検証', () => {
const invalidOrder = { amount: 1000 };
expect(() => createOrder(invalidOrder)).toThrow(
expect.objectContaining({
message: '顧客IDが必要です',
code: 'MISSING_CUSTOMER_ID',
})
);
// timestamp は動的なので、存在のみ確認
try {
createOrder(invalidOrder);
} catch (error) {
expect(error.timestamp).toBeDefined();
expect(error.details.received).toEqual(invalidOrder);
}
});
});
この記事の前半部分では、Jest での例外処理テストの基本概念と基本的な検証パターンを解説しました。続いて同期処理、非同期処理、クラスメソッド、実践的なエラーハンドリングテストについて詳しく学んでいきましょう。
同期処理での例外テスト
関数の引数エラー
関数の引数に関するエラーは、最も頻繁に発生する例外の一つです。適切な引数検証とそのテストを実装しましょう。
必須パラメータのチェック
javascriptdescribe('引数エラーのテスト', () => {
function createUser(name, email, age) {
// 必須パラメータのチェック
if (!name) {
throw new Error('名前は必須です');
}
if (!email) {
throw new Error('メールアドレスは必須です');
}
// 型チェック
if (typeof age !== 'number') {
throw new TypeError('年齢は数値で入力してください');
}
// 範囲チェック
if (age < 0 || age > 150) {
throw new RangeError(
'年齢は0から150の範囲で入力してください'
);
}
return { name, email, age };
}
test('名前が未設定の場合', () => {
expect(() =>
createUser('', 'test@example.com', 25)
).toThrow('名前は必須です');
expect(() =>
createUser(null, 'test@example.com', 25)
).toThrow('名前は必須です');
});
test('メールアドレスが未設定の場合', () => {
expect(() => createUser('太郎', '', 25)).toThrow(
'メールアドレスは必須です'
);
});
test('年齢の型が不正な場合', () => {
expect(() =>
createUser('太郎', 'test@example.com', '25')
).toThrow(TypeError);
expect(() =>
createUser('太郎', 'test@example.com', '25')
).toThrow('年齢は数値で入力してください');
});
test('年齢の範囲が不正な場合', () => {
expect(() =>
createUser('太郎', 'test@example.com', -1)
).toThrow(RangeError);
expect(() =>
createUser('太郎', 'test@example.com', 200)
).toThrow('年齢は0から150の範囲で入力してください');
});
});
計算エラー(ゼロ除算など)
数値計算における例外処理は、特に注意深くテストする必要があります。
数学的エラーの検証
javascriptdescribe('計算エラーのテスト', () => {
class Calculator {
divide(dividend, divisor) {
if (
typeof dividend !== 'number' ||
typeof divisor !== 'number'
) {
throw new TypeError(
'引数は数値である必要があります'
);
}
if (divisor === 0) {
throw new Error('ゼロで割ることはできません');
}
if (!isFinite(dividend) || !isFinite(divisor)) {
throw new Error(
'無限大または NaN は計算できません'
);
}
return dividend / divisor;
}
sqrt(value) {
if (typeof value !== 'number') {
throw new TypeError(
'引数は数値である必要があります'
);
}
if (value < 0) {
throw new Error('負の数の平方根は計算できません');
}
return Math.sqrt(value);
}
factorial(n) {
if (!Number.isInteger(n)) {
throw new TypeError(
'引数は整数である必要があります'
);
}
if (n < 0) {
throw new Error('負の数の階乗は定義されていません');
}
if (n > 170) {
throw new Error('値が大きすぎて計算できません');
}
let result = 1;
for (let i = 2; i <= n; i++) {
result *= i;
}
return result;
}
}
let calculator;
beforeEach(() => {
calculator = new Calculator();
});
describe('divide メソッド', () => {
test('ゼロ除算エラー', () => {
expect(() => calculator.divide(10, 0)).toThrow(
'ゼロで割ることはできません'
);
expect(() => calculator.divide(-5, 0)).toThrow(Error);
});
test('型エラー', () => {
expect(() => calculator.divide('10', 5)).toThrow(
TypeError
);
expect(() => calculator.divide(10, '5')).toThrow(
'引数は数値である必要があります'
);
});
test('無限大や NaN のエラー', () => {
expect(() => calculator.divide(Infinity, 5)).toThrow(
'無限大または NaN は計算できません'
);
expect(() => calculator.divide(10, NaN)).toThrow(
Error
);
});
});
describe('sqrt メソッド', () => {
test('負の数のエラー', () => {
expect(() => calculator.sqrt(-1)).toThrow(
'負の数の平方根は計算できません'
);
expect(() => calculator.sqrt(-100)).toThrow(Error);
});
test('型エラー', () => {
expect(() => calculator.sqrt('4')).toThrow(TypeError);
expect(() => calculator.sqrt(null)).toThrow(
'引数は数値である必要があります'
);
});
});
describe('factorial メソッド', () => {
test('非整数のエラー', () => {
expect(() => calculator.factorial(3.5)).toThrow(
TypeError
);
expect(() => calculator.factorial(3.5)).toThrow(
'引数は整数である必要があります'
);
});
test('負の数のエラー', () => {
expect(() => calculator.factorial(-1)).toThrow(
'負の数の階乗は定義されていません'
);
});
test('大きすぎる値のエラー', () => {
expect(() => calculator.factorial(200)).toThrow(
'値が大きすぎて計算できません'
);
});
});
});
バリデーションエラー
入力値の検証は、アプリケーションの堅牢性を保つ重要な要素です。
複雑なバリデーションロジック
javascriptdescribe('バリデーションエラーのテスト', () => {
class UserValidator {
static validateUser(userData) {
const errors = [];
// 名前の検証
if (
!userData.name ||
userData.name.trim().length === 0
) {
throw new Error('名前は必須です');
}
if (userData.name.length > 50) {
throw new Error(
'名前は50文字以内で入力してください'
);
}
// メールアドレスの検証
if (!userData.email) {
throw new Error('メールアドレスは必須です');
}
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!emailRegex.test(userData.email)) {
throw new Error(
'有効なメールアドレスを入力してください'
);
}
// パスワードの検証
if (!userData.password) {
throw new Error('パスワードは必須です');
}
if (userData.password.length < 8) {
throw new Error(
'パスワードは8文字以上である必要があります'
);
}
if (
!/(?=.*[a-z])(?=.*[A-Z])(?=.*\d)/.test(
userData.password
)
) {
throw new Error(
'パスワードは英大文字、英小文字、数字を含む必要があります'
);
}
return true;
}
static validateAge(age) {
if (age === null || age === undefined) {
throw new Error('年齢が指定されていません');
}
if (
typeof age !== 'number' ||
!Number.isInteger(age)
) {
throw new TypeError('年齢は整数で入力してください');
}
if (age < 0) {
throw new RangeError(
'年齢は0以上である必要があります'
);
}
if (age > 120) {
throw new RangeError(
'年齢は120以下である必要があります'
);
}
return true;
}
}
describe('validateUser メソッド', () => {
test('名前のバリデーション', () => {
// 空文字
expect(() =>
UserValidator.validateUser({ name: '' })
).toThrow('名前は必須です');
// 空白のみ
expect(() =>
UserValidator.validateUser({ name: ' ' })
).toThrow('名前は必須です');
// 長すぎる名前
const longName = 'a'.repeat(51);
expect(() =>
UserValidator.validateUser({ name: longName })
).toThrow('名前は50文字以内で入力してください');
});
test('メールアドレスのバリデーション', () => {
const validName = '太郎';
// 未入力
expect(() =>
UserValidator.validateUser({
name: validName,
email: '',
})
).toThrow('メールアドレスは必須です');
// 不正なフォーマット
const invalidEmails = [
'invalid-email',
'@example.com',
'test@',
'test.example.com',
'test@.com',
];
invalidEmails.forEach((email) => {
expect(() =>
UserValidator.validateUser({
name: validName,
email,
})
).toThrow('有効なメールアドレスを入力してください');
});
});
test('パスワードのバリデーション', () => {
const validData = {
name: '太郎',
email: 'test@example.com',
};
// 未入力
expect(() =>
UserValidator.validateUser({
...validData,
password: '',
})
).toThrow('パスワードは必須です');
// 短すぎる
expect(() =>
UserValidator.validateUser({
...validData,
password: 'abc123',
})
).toThrow(
'パスワードは8文字以上である必要があります'
);
// 複雑性要件を満たさない
const weakPasswords = [
'abcdefgh', // 小文字のみ
'ABCDEFGH', // 大文字のみ
'12345678', // 数字のみ
'Abcdefgh', // 数字なし
'ABCD1234', // 小文字なし
];
weakPasswords.forEach((password) => {
expect(() =>
UserValidator.validateUser({
...validData,
password,
})
).toThrow(
'パスワードは英大文字、英小文字、数字を含む必要があります'
);
});
});
});
describe('validateAge メソッド', () => {
test('null/undefined のエラー', () => {
expect(() => UserValidator.validateAge(null)).toThrow(
'年齢が指定されていません'
);
expect(() =>
UserValidator.validateAge(undefined)
).toThrow('年齢が指定されていません');
});
test('型エラー', () => {
expect(() => UserValidator.validateAge('25')).toThrow(
TypeError
);
expect(() => UserValidator.validateAge(25.5)).toThrow(
'年齢は整数で入力してください'
);
});
test('範囲エラー', () => {
expect(() => UserValidator.validateAge(-1)).toThrow(
RangeError
);
expect(() => UserValidator.validateAge(-1)).toThrow(
'年齢は0以上である必要があります'
);
expect(() => UserValidator.validateAge(121)).toThrow(
'年齢は120以下である必要があります'
);
});
});
});
非同期処理での例外テスト
非同期処理でのエラーハンドリングは、同期処理とは異なるアプローチが必要です。Promise の reject や async/await でのエラーを適切にテストしましょう。
Promise の reject 処理
基本的な Promise reject のテスト
javascriptdescribe('Promise reject のテスト', () => {
function asyncOperation(shouldFail = false) {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (shouldFail) {
reject(
new Error('非同期処理でエラーが発生しました')
);
} else {
resolve('成功');
}
}, 100);
});
}
test('Promise が reject されることを検証', async () => {
// rejects マッチャーを使用
await expect(asyncOperation(true)).rejects.toThrow(
'非同期処理でエラーが発生しました'
);
});
test('Promise が resolve されることを検証', async () => {
// resolves マッチャーを使用
await expect(asyncOperation(false)).resolves.toBe(
'成功'
);
});
});
複雑な非同期エラー処理
javascriptdescribe('複雑な非同期エラー処理', () => {
class AsyncDataProcessor {
async processData(data) {
// 入力値検証
if (!data) {
throw new Error('データが指定されていません');
}
if (!Array.isArray(data)) {
throw new TypeError(
'データは配列である必要があります'
);
}
// 空配列チェック
if (data.length === 0) {
throw new Error('データが空です');
}
// データ処理のシミュレーション
return new Promise((resolve, reject) => {
setTimeout(() => {
try {
const result = data.map((item) => {
if (typeof item !== 'number') {
throw new TypeError(
`数値以外のデータが含まれています: ${item}`
);
}
return item * 2;
});
resolve(result);
} catch (error) {
reject(error);
}
}, 50);
});
}
async fetchUserData(userId) {
if (!userId) {
throw new Error('ユーザーIDが必要です');
}
// 模擬的なAPI呼び出し
return new Promise((resolve, reject) => {
setTimeout(() => {
if (userId === 'invalid') {
reject(new Error('ユーザーが見つかりません'));
} else if (userId === 'server-error') {
reject(
new Error('サーバーエラーが発生しました')
);
} else {
resolve({
id: userId,
name: `ユーザー${userId}`,
});
}
}, 100);
});
}
}
let processor;
beforeEach(() => {
processor = new AsyncDataProcessor();
});
describe('processData メソッド', () => {
test('データが未指定の場合', async () => {
await expect(processor.processData()).rejects.toThrow(
'データが指定されていません'
);
await expect(
processor.processData(null)
).rejects.toThrow('データが指定されていません');
});
test('データが配列でない場合', async () => {
await expect(
processor.processData('string')
).rejects.toThrow(TypeError);
await expect(
processor.processData(123)
).rejects.toThrow('データは配列である必要があります');
});
test('空配列の場合', async () => {
await expect(
processor.processData([])
).rejects.toThrow('データが空です');
});
test('数値以外のデータが含まれる場合', async () => {
await expect(
processor.processData([1, 2, 'invalid', 4])
).rejects.toThrow(
/数値以外のデータが含まれています: invalid/
);
await expect(
processor.processData([1, null, 3])
).rejects.toThrow(TypeError);
});
test('正常なデータ処理', async () => {
await expect(
processor.processData([1, 2, 3, 4])
).resolves.toEqual([2, 4, 6, 8]);
});
});
describe('fetchUserData メソッド', () => {
test('ユーザーIDが未指定の場合', async () => {
await expect(
processor.fetchUserData()
).rejects.toThrow('ユーザーIDが必要です');
await expect(
processor.fetchUserData('')
).rejects.toThrow('ユーザーIDが必要です');
});
test('存在しないユーザーID', async () => {
await expect(
processor.fetchUserData('invalid')
).rejects.toThrow('ユーザーが見つかりません');
});
test('サーバーエラー', async () => {
await expect(
processor.fetchUserData('server-error')
).rejects.toThrow('サーバーエラーが発生しました');
});
test('正常なユーザーデータ取得', async () => {
await expect(
processor.fetchUserData('123')
).resolves.toEqual({
id: '123',
name: 'ユーザー123',
});
});
});
});
async/await での例外検証
async/await を使用した関数の例外処理テストについて詳しく学びましょう。
async/await エラーハンドリングのパターン
javascriptdescribe('async/await 例外検証', () => {
// 模擬的な外部サービス
class ExternalService {
static async authenticate(credentials) {
if (!credentials.username || !credentials.password) {
throw new Error('認証情報が不完全です');
}
if (credentials.username === 'invalid') {
throw new Error('認証に失敗しました');
}
if (credentials.username === 'timeout') {
throw new Error('Request timeout');
}
return {
token: 'valid-token',
userId: credentials.username,
};
}
static async getData(token) {
if (!token) {
throw new Error('認証トークンが必要です');
}
if (token === 'expired') {
throw new Error('トークンが期限切れです');
}
return { data: 'some data' };
}
}
// テスト対象のサービス
class UserService {
async loginUser(username, password) {
try {
const authResult =
await ExternalService.authenticate({
username,
password,
});
const userData = await ExternalService.getData(
authResult.token
);
return {
success: true,
user: {
id: authResult.userId,
token: authResult.token,
data: userData.data,
},
};
} catch (error) {
throw new Error(`ログインエラー: ${error.message}`);
}
}
async processUserBatch(userIds) {
if (!Array.isArray(userIds)) {
throw new TypeError(
'ユーザーIDリストは配列である必要があります'
);
}
const results = [];
const errors = [];
for (const userId of userIds) {
try {
const user = await this.fetchUserById(userId);
results.push(user);
} catch (error) {
errors.push({ userId, error: error.message });
}
}
if (errors.length > 0) {
throw new Error(
`一部のユーザー処理が失敗しました: ${JSON.stringify(
errors
)}`
);
}
return results;
}
async fetchUserById(userId) {
if (!userId) {
throw new Error('ユーザーIDが必要です');
}
if (userId === 'not-found') {
throw new Error('ユーザーが見つかりません');
}
return { id: userId, name: `User ${userId}` };
}
}
let userService;
beforeEach(() => {
userService = new UserService();
});
describe('loginUser メソッド', () => {
test('認証情報不完全エラー', async () => {
await expect(
userService.loginUser('', 'password')
).rejects.toThrow(
'ログインエラー: 認証情報が不完全です'
);
await expect(
userService.loginUser('user', '')
).rejects.toThrow(
'ログインエラー: 認証情報が不完全です'
);
});
test('認証失敗エラー', async () => {
await expect(
userService.loginUser('invalid', 'password')
).rejects.toThrow(
'ログインエラー: 認証に失敗しました'
);
});
test('タイムアウトエラー', async () => {
await expect(
userService.loginUser('timeout', 'password')
).rejects.toThrow('ログインエラー: Request timeout');
});
test('正常なログイン', async () => {
const result = await userService.loginUser(
'validuser',
'password'
);
expect(result.success).toBe(true);
expect(result.user.id).toBe('validuser');
expect(result.user.token).toBe('valid-token');
});
});
describe('processUserBatch メソッド', () => {
test('配列以外の入力エラー', async () => {
await expect(
userService.processUserBatch('not-array')
).rejects.toThrow(TypeError);
await expect(
userService.processUserBatch(123)
).rejects.toThrow(
'ユーザーIDリストは配列である必要があります'
);
});
test('一部ユーザーが見つからない場合のエラー', async () => {
const userIds = ['user1', 'not-found', 'user2'];
await expect(
userService.processUserBatch(userIds)
).rejects.toThrow(/一部のユーザー処理が失敗しました/);
});
test('すべてのユーザーが正常に処理される', async () => {
const userIds = ['user1', 'user2', 'user3'];
const result = await userService.processUserBatch(
userIds
);
expect(result).toHaveLength(3);
expect(result[0]).toEqual({
id: 'user1',
name: 'User user1',
});
});
});
});
- review
新しい自分に会いに行こう!『自分の変え方』村岡大樹の認知科学コーチングで人生リセット
- review
科学革命から AI 時代へ!『サピエンス全史 下巻』ユヴァル・ノア・ハラリが予見する人類の未来
- review
人類はなぜ地球を支配できた?『サピエンス全史 上巻』ユヴァル・ノア・ハラリが解き明かす驚愕の真実
- review
え?世界はこんなに良くなってた!『FACTFULNESS』ハンス・ロスリングが暴く 10 の思い込みの正体
- review
瞬時に答えが出る脳に変身!『ゼロ秒思考』赤羽雄二が贈る思考力爆上げトレーニング
- review
関西弁のゾウに人生変えられた!『夢をかなえるゾウ 1』水野敬也が教えてくれた成功の本質