
本文讲解如何在 JUnit 5 中正确编写测试用例,验证数据库唯一性约束(如重复手机号)触发预期异常,避免误用 assertDoesNotThrow 导致测试失败。
本文讲解如何在 junit 5 中正确编写测试用例,验证数据库唯一性约束(如重复手机号)触发预期异常,避免误用 assertdoesnotthrow 导致测试失败。
在单元测试中验证业务规则(如“禁止添加重复手机号候选人”)时,核心目标不是“不抛异常”,而是确认系统在违反唯一约束时能准确、可捕获地抛出预期异常。你当前的测试逻辑存在根本性误解:assertDoesNotThrow(() -> candidateDAO.create(candidate)) 表示“期望执行不抛任何异常”,但实际场景中,插入重复手机号理应触发数据库唯一约束并抛出异常(如 PersistenceException 包装的 ConstraintViolationException),因此测试必然失败。
✅ 正确做法是使用 assertThrows() 显式声明并验证异常类型与行为:
@Test
@Order(3)
void createCandidate_DuplicatePhone_Test() {
// 准备一个已存在的手机号(确保数据库中已有该号码的记录)
String duplicatePhone = "999999999";
// 构建待插入的候选人(使用已存在的手机号)
Candidate candidate = Candidate.builder()
.fullName("Duplicate Tester")
.dateOfBirth(LocalDate.of(2001, 10, 1))
.gender(Gender.MALE)
.graduationYear(LocalDate.of(2023, 10, 10))
.phone(duplicatePhone) // 关键:复用已存在号码
.email("duplicate@example.com")
.skill("Java")
.foreignLanguage("English")
.level(6)
.cv("CV content")
.allocationStatus(1)
.remark("Test duplicate phone")
.build();
// ✅ 正确断言:期望抛出 PersistenceException(或更具体的 ConstraintViolationException)
PersistenceException exception = assertThrows(PersistenceException.class,
() -> candidateDAO.create(candidate));
// ? 可选增强:进一步验证异常原因是否为约束冲突
assertTrue(exception.getCause() instanceof ConstraintViolationException,
"Expected ConstraintViolationException as root cause");
}? 关键注意事项:
-
前置数据准备:测试前必须确保数据库中已存在相同手机号的记录(可通过
@BeforeEach插入,或使用@Sql脚本初始化),否则无法触发重复约束; -
异常类型选择:JPA/Hibernate 通常将底层 SQL 约束异常包装为
PersistenceException,其getCause()才是ConstraintViolationException。若需更高精度,可直接assertThrows(ConstraintViolationException.class, ...)—— 但需确保异常未被上层拦截吞没; -
事务管理:确保测试运行在事务中(如
@Transactional),避免脏数据残留;若 DAO 方法本身开启新事务,需确认传播行为兼容; -
不要忽略异常消息:生产环境建议校验异常消息是否包含
"Duplicate entry"或"UK_"等关键词(使用exception.getMessage().contains(...)),提升断言健壮性。
? 总结:验证失败场景 ≠ 期望静默成功;而是要精准捕获、明确断言、合理归因。assertThrows 是测试防御性编程和约束合规性的标准工具,掌握它,才能写出真正可靠的集成/端到端测试。

















