unit-test-exception-handler
Spring Bootアプリケーションにおける`@ExceptionHandler`および`@ControllerAdvice`のユニットテストパターンを提供します。エラーレスポンスのフォーマット検証、例外のモック、HTTPステータスコードの確認、フィールドレベルのバリデーションエラーテスト、カスタムエラーペイロードのアサーションに対応します。Spring例外ハンドラーのテスト、REST APIエラーテスト、またはController Adviceのモックを記述する際に活用してください。
description の原文を見る
Provides patterns for unit testing `@ExceptionHandler` and `@ControllerAdvice` in Spring Boot applications. Validates error response formatting, mocks exceptions, verifies HTTP status codes, tests field-level validation errors, and asserts custom error payloads. Use when writing Spring exception handler tests, REST API error tests, or mocking controller advice.
SKILL.md 本文
ExceptionHandler と ControllerAdvice のユニットテスト
概要
このスキルは Spring Boot 例外ハンドラのユニットテストを書くためのパターンを提供します。MockMvc を使用した @ControllerAdvice クラス内の @ExceptionHandler メソッドのテスト、HTTP ステータスのアサーション、JSON レスポンス検証、フィールドレベルの検証エラーテスト、およびハンドラ依存関係のモック化に対応します。
使用する場合
@ExceptionHandlerメソッドのユニットテストを書く場合@ControllerAdviceグローバル例外処理をテストする場合- REST API エラーレスポンスのフォーマットを検証する場合
- コントローラテストで例外をモック化する場合
- フィールドレベルの検証エラーレスポンスをテストする場合
- カスタムエラーペイロードと HTTP ステータスコードをアサートする場合
手順
- テストコントローラを作成 して各
@ExceptionHandlerをトリガーする特定の例外をスロー - ControllerAdvice を登録 -
MockMvcBuilders.standaloneSetup()上でsetControllerAdvice()を使用 - HTTP ステータスコードをアサート -
.andExpect(status().isXxx())で確認 - エラーレスポンスフィールドを検証 -
jsonPath("$.field")マッチャーを使用 - 検証エラーをテスト - 無効なペイロードを送信して
MethodArgumentNotValidExceptionがフィールドレベルの詳細を生成することを確認 - 失敗をデバッグ -
.andDo(print())を使用 — ハンドラが呼び出されない場合はsetControllerAdvice()が呼ばれていること、例外タイプがマッチしていることを確認
例
例外ハンドラとエラー DTO
@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(ResourceNotFoundException.class)
@ResponseStatus(HttpStatus.NOT_FOUND)
public ErrorResponse handleNotFound(ResourceNotFoundException ex) {
return new ErrorResponse(404, "Not Found", ex.getMessage());
}
@ExceptionHandler(ValidationException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
public ErrorResponse handleValidation(ValidationException ex) {
return new ErrorResponse(400, "Bad Request", ex.getMessage());
}
@ExceptionHandler(MethodArgumentNotValidException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
public ValidationErrorResponse handleMethodArgumentNotValid(MethodArgumentNotValidException ex) {
Map<String, String> errors = new HashMap<>();
ex.getBindingResult().getFieldErrors().forEach(e -> errors.put(e.getField(), e.getDefaultMessage()));
return new ValidationErrorResponse(400, "Validation Failed", errors);
}
}
public record ErrorResponse(int status, String error, String message) {}
public record ValidationErrorResponse(int status, String error, Map<String, String> errors) {}
ユニットテスト
@ExtendWith(MockitoExtension.class)
class GlobalExceptionHandlerTest {
private MockMvc mockMvc;
@BeforeEach
void setUp() {
GlobalExceptionHandler handler = new GlobalExceptionHandler();
mockMvc = MockMvcBuilders.standaloneSetup(new TestController())
.setControllerAdvice(handler)
.build();
}
@Test
void shouldReturn404WhenResourceNotFound() throws Exception {
mockMvc.perform(get("/api/users/999"))
.andExpect(status().isNotFound())
.andExpect(jsonPath("$.status").value(404))
.andExpect(jsonPath("$.error").value("Not Found"))
.andExpect(jsonPath("$.message").value("User not found"));
}
@Test
void shouldReturn400WithFieldErrorsOnValidationFailure() throws Exception {
mockMvc.perform(post("/api/users")
.contentType("application/json")
.content("{\"name\":\"\",\"email\":\"invalid\"}"))
.andExpect(status().isBadRequest())
.andExpect(jsonPath("$.status").value(400))
.andExpect(jsonPath("$.errors.name").value("must not be blank"))
.andExpect(jsonPath("$.errors.email").value("must be a valid email"));
}
}
@RestController
@RequestMapping("/api")
class TestController {
@GetMapping("/users/{id}") public User getUser(@PathVariable Long id) {
throw new ResourceNotFoundException("User not found");
}
@PostMapping("/users") public User createUser(@RequestBody @Valid User user) {
throw new ValidationException("Validation failed");
}
}
ベストプラクティス
- 各
@ExceptionHandlerメソッドを独立してテスト - 特定の例外をスロー setControllerAdvice()を使用して正確に 1 つの@ControllerAdviceインスタンスを登録 — スキップしないこと- HTTP ステータスだけでなく、エラーレスポンス本体のすべてのフィールドをアサート
- 検証エラーについては、フィールド名キーとエラーメッセージの値の両方を検証
MockMvcBuilders.standaloneSetup()を使用 - 完全な Spring コンテキストなしで、隔離されたハンドラテストの場合- アサーション失敗をログに記録 -
.andDo(print())をチェーンして、テスト失敗時に要求/レスポンスを出力
よくある落とし穴
- ハンドラが呼び出されない: ビルダー上で
setControllerAdvice()が呼ばれていることを確認 - JsonPath 不一致:
.andDo(print())を使用して、実際のレスポンス構造を検査 - ステータスが 200: ハンドラメソッドに
@ResponseStatusがないか、ResponseEntityを返していない - ハンドラの重複:
@Orderが優先度を制御します;より具体的な例外タイプが優先される - ハンドラロジックをテスト: 外部依存関係をモック化し、レスポンス変換のみをテスト
制約と警告
@ExceptionHandlerの具体性: より具体的な例外タイプがまず最初にマッチされます;Exception.classはマッチしないすべてのタイプをキャッチ@ResponseStatusのデフォルト:@ResponseStatusまたはResponseEntityを返さない場合、HTTP ステータスはデフォルトで 200- グローバル vs ローカルスコープ:
@ControllerAdvice内の@ExceptionHandlerはグローバル;コントローラで宣言された場合、そのコントローラのみにローカル - ロギング副作用: ハンドラがログを記録する場合、
verify(mockLogger).logXxx(...)で検証する必要があります - ローカライゼーション:
MessageSourceを使用する場合、異なるLocale値でテストしてメッセージ解決を確認 - セキュリティコンテキスト:
AuthorizationExceptionハンドラはSecurityContextHolderにアクセス可能 — コンテキストが正しく評価されることをテスト
ライセンス: MIT(寛容ライセンスのため全文を引用しています) · 原本リポジトリ
詳細情報
- 作者
- giuseppe-trisciuoglio
- ライセンス
- MIT
- 最終更新
- 不明
Source: https://github.com/giuseppe-trisciuoglio/developer-kit / ライセンス: MIT
関連スキル
doubt-driven-development
重要な判断はすべて、本番環境への展開前に新しい視点から対抗的レビューを実施します。速度より正確性が重要な場合、不慣れなコードを扱う場合、本番環境・セキュリティに関わるロジック・取り消し不可の操作など影響度が高い場合、または後でバグを修正するよりも今検証する方が効率的な場合に活用してください。
apprun-skills
TypeScriptを使用したAppRunアプリケーションのMVU設計に関する総合的なガイダンスが得られます。コンポーネントパターン、イベントハンドリング、状態管理(非同期ジェネレータを含む)、パラメータと保護機能を備えたルーティング・ナビゲーション、vistestを使用したテストに対応しています。AppRunコンポーネントの設計・レビュー、ルートの配線、状態フローの管理、AppRunテストの作成時に活用してください。
desloppify
コードベースのヘルスチェックと技術負債の追跡ツールです。コード品質、技術負債、デッドコード、大規模ファイル、ゴッドクラス、重複関数、コードスメル、命名規則の問題、インポートサイクル、結合度の問題についてユーザーが質問した場合に使用してください。また、ヘルススコアの確認、次の改善項目の提案、クリーンアップ計画の作成をリクエストされた際にも対応します。29言語に対応しています。
debugging-and-error-recovery
テストが失敗したり、ビルドが壊れたり、動作が期待と異なったり、予期しないエラーが発生したりした場合に、体系的な根本原因デバッグをガイドします。推測ではなく、根本原因を見つけて修正するための体系的なアプローチが必要な場合に使用してください。
test-driven-development
テスト駆動開発により実装を進めます。ロジックの実装、バグの修正、動作の変更など、あらゆる場面で活用できます。コードが正常に動作することを証明する必要がある場合、バグ報告を受けた場合、既存機能を修正する予定がある場合に使用してください。
incremental-implementation
変更を段階的に実施します。複数のファイルに影響する機能や変更を実装する場合に使用してください。大量のコードを一度に書こうとしている場合や、タスクが一度では完結できないほど大きい場合に活用します。