如何对JpaSystemException的Oracle错误码处理逻辑做单元测试?
问题
我有一个调用Oracle函数的JpaRepository,该函数会返回若干应用错误。我已通过工具方法pullOracleErrorCodeFromJpaException提取错误码,并在JpaSystemException的catch块中根据不同错误码抛出对应业务异常。目前功能正常,但因为错误码嵌套在两层异常(JpaSystemException → 中间异常 → SQLException)中,难以对不同错误码的处理逻辑做单元测试。请问能否用org.mockito.Mockito.doThrow构造两层嵌套异常,来验证各错误码是否抛出对应异常?
解决方案
完全可以用Mockito构造嵌套异常实现测试,核心是逐层包装异常:先构造带指定错误码的SQLException,再用JPA相关中间异常(如PersistenceException)包装它,最后将中间异常作为JpaSystemException的cause抛出,再通过Mock让Repository方法抛出这个嵌套异常。
具体实现步骤
- 构造目标
SQLException,传入指定错误码 - 用
PersistenceException(JPA底层常用的中间异常类型)包装SQLException - 用
JpaSystemException包装上述PersistenceException - 通过
Mockito.doThrow()配置Mock的Repository方法抛出这个嵌套好的异常
测试代码示例
假设业务服务类为PersonService,依赖PersonRepository,测试类可参考如下写法:
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; import org.springframework.orm.jpa.JpaSystemException; import javax.persistence.PersistenceException; import java.sql.SQLException; import static org.junit.jupiter.api.Assertions.*; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.doThrow; @ExtendWith(MockitoExtension.class) class PersonServiceTest { @Mock private PersonRepository personRepository; @InjectMocks private PersonService personService; // 测试错误码20001的分支逻辑 @Test void whenOracleError20001_thenThrowInvalidRequestException() { // 构造带指定错误码的SQLException SQLException sqlException = new SQLException("Test error", "", 20001); // 用PersistenceException包装SQLException PersistenceException persistenceException = new PersistenceException(sqlException); // 用JpaSystemException包装中间异常 JpaSystemException jpaSystemException = new JpaSystemException(persistenceException); // 配置Mock的Repository方法抛出嵌套异常 doThrow(jpaSystemException) .when(personRepository) .findPersonProfile(anyString(), anyString(), anyString()); // 验证是否抛出对应业务异常 InvalidRequestException exception = assertThrows(InvalidRequestException.class, () -> { personService.getPersonProfile(new PersonRequest()); }); assertEquals("At least one variable is required to run the acp usac function.", exception.getMessage()); } // 测试未识别错误码的分支逻辑 @Test void whenUnrecognizedOracleError_thenThrowOracleErrorException() { SQLException sqlException = new SQLException("Unknown error", "", 99999); PersistenceException persistenceException = new PersistenceException(sqlException); JpaSystemException jpaSystemException = new JpaSystemException(persistenceException); doThrow(jpaSystemException) .when(personRepository) .findPersonProfile(anyString(), anyString(), anyString()); OracleErrorException exception = assertThrows(OracleErrorException.class, () -> { personService.getPersonProfile(new PersonRequest()); }); assertEquals("Oracle function returned an unrecognized application error.", exception.getMessage()); } // 测试无法提取错误码的分支逻辑 @Test void whenCannotExtractErrorCode_thenThrowOracleErrorException() { // 构造不包含SQLException的嵌套异常 JpaSystemException jpaSystemException = new JpaSystemException(new RuntimeException("Non-SQL error")); doThrow(jpaSystemException) .when(personRepository) .findPersonProfile(anyString(), anyString(), anyString()); OracleErrorException exception = assertThrows(OracleErrorException.class, () -> { personService.getPersonProfile(new PersonRequest()); }); assertEquals("Oracle Function returned an error.", exception.getMessage()); } }
注意事项
- 中间层异常尽量使用生产环境中JPA实际抛出的类型(如
PersistenceException),保证异常链和真实场景一致 - 若
pullOracleErrorCodeFromJpaException是私有方法,无需单独测试,通过验证业务方法的异常抛出逻辑即可间接验证该工具方法的正确性 - 可将构造嵌套异常的逻辑封装成工具方法,减少测试代码重复
内容的提问来源于stack exchange,提问作者alaskanloops
相关产品推荐
相关产品推荐

