如何用JUnit 5和Mockito测试含异常处理的服务层更新方法
问题解决思路与修正代码
核心问题分析
你当前的测试代码存在两个关键错误:
- 错误地对被测试的Service实例(
syndromeTypeServiceMock,由@InjectMocks注入的真实实现)使用given(...)来Mock方法,这不符合Mockito的使用逻辑——我们应该Mock的是Service依赖的Repository,而非Service本身。 - 未对
syndromeTypeTransRepository.findById()方法设置Mock行为,导致它返回空Optional,触发Service中的异常校验逻辑。
下面分两种场景给出修正方案:
场景1:测试正常修改流程(synTypeTransId存在)
需要Mock Repository的两个关键方法:
findById()返回存在的SyndromeTypeTransEntity实例,避免触发异常save()返回构造好的实体,让Service能生成预期的返回结果
修正后的测试代码:
@ExtendWith(MockitoExtension.class) public class SyndromeTypeServiceTest { Logger LOGGER = LoggerFactory.getLogger(getClass()); @Mock SyndromeTypeRepository syndromeTypeRepository; @Mock SyndromeTypeTransRepository syndromeTypeTransRepository; @InjectMocks SyndromeTypeServiceImpl syndromeTypeService; // 改名更贴合真实实例 @Test public void test_modify_SyndromeType_when_synTransId_exist() throws PersistenceException { LOGGER.info("test_modify_SyndromeType_when_synTransId_exist >>"); // 1. 构造测试用的请求对象 SyndromeType synTypeRequest = SyndromeType.builder() .synTypeId(10) .synTypeName("Musculoesquelético.") .lanCode("es") .synTypeTransId(10) .build(); // 2. Mock Repository findById的返回值:构造存在的SyndromeTypeTransEntity SyndromeTypeTransEntity existingTransEntity = SyndromeTypeTransEntity.builder() .synTypeTransId(10) .synTypeEtt(SyndromeTypeEntity.builder().synTypeId(10).build()) .lanCodeEtt(LanguageEntity.builder().lanCode("es").build()) .synTypeName("Original Name") .build(); BDDMockito.given(syndromeTypeTransRepository.findById(10)).willReturn(Optional.of(existingTransEntity)); // 3. Mock Repository save的返回值:构造保存后的实体 SyndromeTypeTransEntity savedTransEntity = SyndromeTypeTransEntity.builder() .synTypeTransId(10) .synTypeEtt(SyndromeTypeEntity.builder().synTypeId(10).build()) .lanCodeEtt(LanguageEntity.builder().lanCode("es").build()) .synTypeName("Musculoesquelético.") .build(); BDDMockito.given(syndromeTypeTransRepository.save(BDDMockito.any(SyndromeTypeTransEntity.class))).willReturn(savedTransEntity); // 4. 调用真实的Service方法 SyndromeType actualResult = syndromeTypeService.modifySyndromeType(synTypeRequest); // 5. 断言结果符合预期 assertNotNull(actualResult); assertEquals(synTypeRequest.getSynTypeTransId(), actualResult.getSynTypeTransId()); assertEquals(synTypeRequest.getSynTypeId(), actualResult.getSynTypeId()); assertEquals(synTypeRequest.getSynTypeName(), actualResult.getSynTypeName()); assertEquals(synTypeRequest.getLanCode(), actualResult.getLanCode()); LOGGER.info("<< test_modify_SyndromeType_when_synTransId_exist"); } }
场景2:测试异常流程(synTypeTransId不存在,抛出PersistenceException)
需要让findById()返回空Optional,然后验证Service是否正确抛出异常。
测试代码:
@Test public void test_modify_SyndromeType_when_synTransId_not_exist() { LOGGER.info("test_modify_SyndromeType_when_synTransId_not_exist >>"); // 构造请求对象,传入不存在的synTypeTransId SyndromeType synTypeRequest = SyndromeType.builder() .synTypeId(999) .synTypeName("Test") .lanCode("es") .synTypeTransId(999) .build(); // Mock findById返回空Optional BDDMockito.given(syndromeTypeTransRepository.findById(999)).willReturn(Optional.empty()); // 验证调用Service方法时抛出指定异常 PersistenceException exception = assertThrows(PersistenceException.class, () -> syndromeTypeService.modifySyndromeType(synTypeRequest)); // 可选:断言异常信息符合预期 assertEquals(ErrorMessage.RECORD_NOT_EXIST.name(), exception.getMessage()); LOGGER.info("<< test_modify_SyndromeType_when_synTransId_not_exist"); }
关键要点总结
- 永远不要Mock被测试的类本身:
@InjectMocks注入的是真实的Service实例,我们只需要Mock它的依赖(Repository)来控制执行流程。 - 精准Mock依赖方法的行为:根据测试场景,设置Repository方法返回预期值(存在的实体或空Optional),让Service按我们设计的路径执行。
- 异常验证用
assertThrows:JUnit 5的assertThrows可以直接捕获并验证方法抛出的异常类型和信息。
内容的提问来源于stack exchange,提问作者Marco Osorio
相关产品推荐
相关产品推荐

