mcpField
U
unit-test-bean-validation

unit-test-bean-validation

github.com/giuseppe-trisciuoglio测试与质量3654.9k 次安装7 周前更新
代码仓库

关于 unit-test-bean-validation

使用 JUnit 5 测试 Jakarta Bean Validation 注解和自定义验证器的指南,适合希望脱离 Spring 上下文隔离测试约束的 Java 开发者。

主要功能

  1. 01内置约束测试
  2. 02自定义验证器测试
  3. 03跨字段验证
  4. 04验证组测试
  5. 05参数化测试模式
  6. 06断言违规详情

适用场景

  1. 01测试 DTO 验证规则
  2. 02检查自定义约束消息
  3. 03验证密码匹配规则
  4. 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 上下文即可快速进行验证测试

操作说明

  1. 添加依赖项:在测试作用域中加入 jakarta.validation-api 和 hibernate-validator
  2. 创建基础测试类:在 @BeforeEach 中使用 Validation.buildDefaultValidatorFactory() 创建一次 Validator
  3. 先测试有效用例:验证对象通过校验且没有违规
  4. 测试无效用例:断言约束违规包含正确的属性路径和消息
  5. 提取违规详情:使用 getPropertyPath()、getMessage()、getInvalidValue()
  6. 测试自定义验证器:查看 references/custom-validators.md 中的模式
  7. 使用参数化测试:通过 @ParameterizedTest 高效测试多个输入
  8. 测试验证组:使用验证组处理条件规则(参见 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 上。

参考资料

评论

支持 Markdown