关于 unit-test-bean-validation
使用 JUnit 5 测试 Jakarta Bean Validation 注解和自定义验证器的指南,适合希望脱离 Spring 上下文隔离测试约束的 Java 开发者。
主要功能
- 01内置约束测试
- 02自定义验证器测试
- 03跨字段验证
- 04验证组测试
- 05参数化测试模式
- 06断言违规详情
适用场景
- 01测试 DTO 验证规则
- 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实例是线程安全的,可以共享 - 消息本地化:如需支持国际化,请使用不同的区域设置进行测试
- 级联验证:在嵌套对象上使用
@Valid进行递归验证 - 自定义验证器:必须保持无状态,并对 null 值返回
true - 测试隔离:验证单元测试不应依赖 Spring 上下文或数据库
故障排查
未找到 ValidatorFactory:确保测试类路径中包含 jakarta.validation-api 和 hibernate-validator。
未调用自定义验证器:确认 @Constraint(validatedBy = YourValidator.class) 注解正确。
空值通过验证:这是预期行为——除非存在 @NotNull,否则约束会忽略空值。
违规数量错误:使用 hasSize() 验证准确数量,并检查对象中的所有字段。
属性路径不正确:确保约束注解标注在字段上,而不是 getter 上。
参考资料
- Jakarta Bean Validation 规范
- Hibernate Validator
- 自定义验证器和跨字段验证:
references/custom-validators.md - 验证组和参数化测试:
references/advanced-patterns.md
评论
支持 Markdown