unit-test-bean-validation について
JUnit 5 で Jakarta Bean Validation のアノテーションとカスタムバリデーターをテストするガイドです。Spring コンテキストを使わずに制約を単体テストしたい Java 開発者向けです。
主な機能
- 01組み込み制約のテスト
- 02カスタムバリデーターのテスト
- 03フィールド間バリデーション
- 04バリデーショングループのテスト
- 05パラメータ化テストのパターン
- 06違反詳細のアサーション
活用シーン
- 01DTO の検証ルールのテスト
- 02カスタム制約メッセージの確認
- 03パスワード一致ルールの検証
- 04条件付きバリデーショングループのテスト
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ giuseppe-trisciuoglio/ developer-kit --skill unit-test-bean-validationインストール後、Claude Code で/unit-test-bean-validationを入力して読み込まれたか確認します。
同梱ファイルreferences/advanced-patterns.mdreferences/custom-validators.md
詳細
Jakarta Bean Validation のユニットテスト
概要
このスキルでは、JUnit 5 を使って Jakarta Bean Validation のアノテーションやカスタムバリデーターをユニットテストするための、実行可能なパターンを紹介します。組み込み制約(@NotNull、@Email、@Min、@Max、@Size)、カスタムの @Constraint 実装、フィールド間検証、検証グループを扱います。テストは Spring のコンテキストなしで独立して実行できます。
使用する場面
- Jakarta Bean Validation または JSR-380 の制約のユニットテストを作成するとき
- カスタムの
@Constraintバリデーターと制約違反メッセージをテストするとき - DTO やリクエストオブジェクトの Bean Validation ロジックをテストするとき
- フィールド間検証(パスワードの一致など)を検証するとき
- 検証グループを使った条件付き検証をテストするとき
- Spring Boot のコンテキストなしで高速な検証テストを行うとき
手順
- 依存関係を追加する:
jakarta.validation-apiとhibernate-validatorをテストスコープに含めます - ベーステストクラスを作成する:
@BeforeEachでValidation.buildDefaultValidatorFactory()を使い、Validatorを一度だけ作成します - まず有効なケースをテストする:オブジェクトが違反なしで通ることを検証します
- 無効なケースをテストする:制約違反に正しいプロパティパスとメッセージが含まれることをアサートします
- 違反の詳細を取得する:
getPropertyPath()、getMessage()、getInvalidValue()を使います - カスタムバリデーターをテストする:パターンについては
references/custom-validators.mdを参照してください - パラメーター化テストを使う:
@ParameterizedTestで複数の入力を効率よくテストします - グループ検証をテストする:条件付きルールには検証グループを使います(
references/advanced-patterns.mdを参照)
例
Maven の設定
<dependency>
<groupId>jakarta.validation</groupId>
<artifactId>jakarta.validation-api</artifactId>
</dependency>
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<scope>test</scope>
</dependency>
共通のテスト設定
import jakarta.validation.*;
import jakarta.validation.ConstraintViolation;
import jakarta.validation.path.Path;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.*;
class BaseValidationTest {
protected Validator validator;
@BeforeEach
void setUpValidator() {
validator = Validation.buildDefaultValidatorFactory().getValidator();
}
}
基本的な制約のテスト
class UserDtoTest extends BaseValidationTest {
@Test
void shouldPassValidationWithValidUser() {
UserDto user = new UserDto("Alice", "[email protected]", 25);
assertThat(validator.validate(user)).isEmpty();
}
@Test
void shouldFailWhenNameIsNull() {
UserDto user = new UserDto(null, "[email protected]", 25);
assertThat(validator.validate(user))
.extracting(ConstraintViolation::getMessage)
.contains("must not be blank");
}
@Test
void shouldFailWhenEmailIsInvalid() {
UserDto user = new UserDto("Alice", "invalid-email", 25);
Set<ConstraintViolation<UserDto>> violations = validator.validate(user);
assertThat(violations)
.extracting(ConstraintViolation::getPropertyPath)
.extracting(Path::toString)
.contains("email");
}
@Test
void shouldFailWhenAgeIsBelowMinimum() {
UserDto user = new UserDto("Alice", "[email protected]", -1);
assertThat(validator.validate(user))
.extracting(ConstraintViolation::getMessage)
.contains("must be greater than or equal to 0");
}
@Test
void shouldFailWhenMultipleConstraintsViolated() {
UserDto user = new UserDto(null, "invalid", -5);
assertThat(validator.validate(user)).hasSize(3);
}
}
カスタムバリデーターのテスト
カスタム制約のパターンについては、references/custom-validators.md を参照してください:
@Constraintアノテーションの作成ConstraintValidatorの実装- フィールド間検証(パスワードの一致)
- ステートレスなバリデーターのベストプラクティス
検証グループのテスト
検証グループとパラメーター化テストについては、references/advanced-patterns.md を参照してください:
- 検証グループインターフェースの定義
groupsパラメーターを使った条件付き検証@ValueSourceと@CsvSourceを使った@ParameterizedTest- 失敗した検証テストのデバッグ
ベストプラクティス
- 有効なケースと無効なケースの両方をテストする:すべての制約について、成功するテストケースと失敗するテストケースの両方が必要です
- 違反の詳細をアサートする:プロパティパス、メッセージ、制約の種類を検証します
- エッジケースをテストする:null、空文字列、空白のみ、境界値
- バリデーターをステートレスに保つ:カスタムバリデーターは状態を保持してはいけません
- わかりやすいメッセージを使う:制約メッセージはユーザーにとってわかりやすいものにします
- 関連するテストをまとめる:
BaseValidationTestを拡張し、バリデーターの設定を共有します - エラーメッセージをテストする:メッセージが要件と一致することを確認します
よくある落とし穴
- null 値のテストを忘れる(ほとんどの制約はデフォルトで null を無視します)
- 制約違反のプロパティパスを検証しない
- ユニットレベルではなく、サービス/コントローラーレベルで検証をテストする
- カスタムバリデーターを複雑にしすぎる
- 他の制約と組み合わせる必須フィールドに
@NotNullがない
制約と注意事項
- null の処理:ほとんどの制約はデフォルトで null を無視します。必須フィールドには他の制約と
@NotNullを組み合わせてください - スレッドセーフ:
Validatorインスタンスはスレッドセーフで、共有できます - メッセージのローカライズ:i18n が必要な場合は、異なるロケールでテストしてください
- カスケード検証:ネストされたオブジェクトに
@Validを使うと、再帰的に検証できます - カスタムバリデーター:ステートレスであり、null 値に対して
trueを返す必要があります - テストの独立性:検証のユニットテストは Spring のコンテキストやデータベースに依存させないでください
トラブルシューティング
ValidatorFactory が見つからない:jakarta.validation-api と hibernate-validator がテストクラスパスに含まれていることを確認してください。
カスタムバリデーターが呼び出されない:@Constraint(validatedBy = YourValidator.class) アノテーションが正しいことを確認してください。
null 値が検証を通過する:これは想定どおりの動作です。@NotNull が指定されていない場合、制約は null を無視します。
違反件数が正しくない:hasSize() を使用して正確な件数を確認し、オブジェクト内のすべてのフィールドを確認してください。
プロパティパスが正しくない:制約アノテーションがゲッターではなくフィールドに付いていることを確認してください。
参考資料
- Jakarta Bean Validation 仕様
- Hibernate Validator
- カスタムバリデーターとフィールド間の検証:
references/custom-validators.md - 検証グループとパラメーター化テスト:
references/advanced-patterns.md