
本文讲解在 JUnit 5 中如何正确验证数据库唯一性约束触发的异常,重点纠正误用 assertDoesNotThrow() 导致测试失败的问题,并演示使用 assertThrows() 捕获并断言预期的 PersistenceException 或其子类。
本文讲解在 junit 5 中如何正确验证数据库唯一性约束触发的异常,重点纠正误用 `assertdoesnotthrow()` 导致测试失败的问题,并演示使用 `assertthrows()` 捕获并断言预期的 `persistenceexception` 或其子类。
当编写单元测试验证数据库层对重复数据(如手机号)的防护逻辑时,一个常见误区是:期望“不抛异常”,却实际需要验证“必须抛出特定异常”。你当前的 createCandidate_DuplicatePhone_Test 测试失败的根本原因在于语义矛盾——业务规则要求“重复手机号应拒绝插入”,因此系统必须抛出异常;而 assertDoesNotThrow() 却在断言“绝不应抛异常”,自然导致测试崩溃。
✅ 正确做法是使用 assertThrows() 显式声明并验证预期异常:
@Test
@Order(3)
void createCandidate_DuplicatePhone_Test() {
// 准备一个已存在的手机号(需确保该号码已在DB中存在,或通过事务/测试数据预置)
String duplicatePhone = "999999999";
Candidate candidate = Candidate.builder()
.fullName("Name")
.dateOfBirth(LocalDate.of(2001, 10, 1))
.gender(Gender.MALE)
.graduationYear(LocalDate.of(2023, 10, 10))
.phone(duplicatePhone) // 关键:复用已存在的手机号
.email("test@example.com")
.skill("java")
.foreignLanguage("English")
.level(6)
.cv("Good")
.allocationStatus(1)
.remark("Not Bad")
.build();
// ✅ 正确断言:期望 PersistenceException(或更具体的 ConstraintViolationException)
Throwable thrown = assertThrows(PersistenceException.class, () -> {
candidateDAO.create(candidate);
});
// ? 进一步校验异常根源(推荐)
assertTrue(thrown.getCause() instanceof ConstraintViolationException,
"Expected ConstraintViolationException as root cause");
}
⚠️ 关键注意事项:
-
测试数据准备:确保被测手机号
"999999999"在执行candidateDAO.create()前已存在于数据库中(可通过@BeforeEach插入、@Sql脚本或 Testcontainers 初始化); -
异常类型选择:
- 若 DAO 层直接暴露 JPA 异常,优先捕获
jakarta.persistence.PersistenceException; - 若需更精确匹配数据库约束违规,可捕获
org.hibernate.exception.ConstraintViolationException(注意包路径与 Hibernate 版本适配);
- 若 DAO 层直接暴露 JPA 异常,优先捕获
-
避免空断言:仅调用
assertThrows()不够健壮,建议结合assertTrue(thrown.getCause() instanceof ...)验证异常链,防止误捕其他无关异常; -
事务管理:确保测试方法运行在独立事务中(如
@Transactional),避免脏数据污染后续测试。
? 总结:测试“失败场景”不是为了掩盖错误,而是主动声明并验证系统的防御能力。assertThrows() 是验证业务规则强制约束的黄金标准——它让测试从“意外崩溃”转变为“精准证伪”,真正体现 TDD 中“先写失败测试”的设计哲学。










