如何实现Spring项目中insertEvent方法的100%测试覆盖率
问题:Spring中PreparedStatementCallback代码块未执行,测试覆盖率无法达100%
我在Spring项目里调试时发现insertEvent方法中的doInPreparedStatement没有执行,导致PreparedStatementCallback代码块没被覆盖,测试覆盖率达不到100%。
核心业务代码
@Override public int insertEvent(JobStatusDTO jobStatusDTO) throws AccountUpdateException { LOGGER.info("In insertAccountUpdateEvent- {}", jobStatusDTO); try { jdbcTemplate.execute("INSERT INTO EVENT_LOG (JOB_EXECUTION_ID, FILE_NAME, STATUS, TOTAL_EVENTS_RECD, TOTAL_EVENTS_PROCESSED, TOTAL_EVENTS_FAILED, FILE_PROCESS_START_TIME, FILE_PROCESS_END_TIME, PROCESS_MESSAGE_N) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)", new PreparedStatementCallback<Boolean>() { @Override public Boolean doInPreparedStatement(PreparedStatement ps) throws SQLException, DataAccessException { ps.setLong(1, jobStatusDTO.getJobId()); ps.setString(2, jobStatusDTO.getFileName()); ps.setString(3, jobStatusDTO.getJobStatus()); ps.setLong(4, jobStatusDTO.getTotalEvents()); ps.setLong(5, jobStatusDTO.getTotalSuccessEvents()); ps.setLong(6, jobStatusDTO.getTotalFailedEvents()); ps.setTimestamp(7, new Timestamp(jobStatusDTO.getJobStartTime().getTime())); ps.setTimestamp(8, new Timestamp(jobStatusDTO.getJobEndTime().getTime())); ps.setString(9, processErrorMessage(jobStatusDTO)); return ps.execute(); } }); } catch (DataAccessException e) { throw new AccountUpdateException(Error.ERR_080.getErrorMessage(), e.getMessage()); } catch (Exception e) { throw new AccountUpdateException(Error.ERR_081.getErrorMessage(), e.getMessage()); } return 0; }
我编写的单元测试
class AccountUpdateAuditDAOImplTest { @InjectMocks AccountUpdateAuditDAOImpl accountUpdateAuditDAOImpl; @Mock JdbcTemplate jdbcTemplate; @Mock ResultSet set; @Mock NamedParameterJdbcTemplate namedTemplate; @SuppressWarnings("rawtypes") @Captor ArgumentCaptor argCaptor; @BeforeEach void setup() throws Exception { MockitoAnnotations.openMocks(this); } @SuppressWarnings("unchecked") @Test void testInsertAccountUpdateEvent() throws AccountUpdateException, Exception { when(jdbcTemplate.execute(Mockito.anyString(), Mockito.any(PreparedStatementCallback.class))).thenReturn(true); JobStatusDTO dTO = createMockDTO(); PreparedStatement ps = mock(PreparedStatement.class); ps.setLong(1, dTO.getJobId()); List<JobStatusDTO> list = new ArrayList<JobStatusDTO>(); dTO.setJobId(1); list.add(dTO); List<Map<String, Object>> value = new ArrayList<Map<String, Object>>(); Map<String, Object> map =new HashMap<String, Object>(); map.put("Job_Id", "1"); map.put("File_Name", "RTAU_Btach"); map.put("Job_Status", "Completed"); map.put("Total_Event", "10"); map.put("Success_Event", "7"); map.put("Failure_Event", "3"); value.add(map); Mockito.when(namedTemplate.queryForList(Mockito.anyString(), Mockito.anyMap())).thenReturn(value); assertDoesNotThrow(() -> accountUpdateAuditDAOImpl.insertAccountUpdateEvent(dTO)); } private JobStatusDTO createMockDTO() { JobStatusDTO jobStatusDTO = new JobStatusDTO(); jobStatusDTO.setFileName("rtau_pulse_20220624104610853.txt"); jobStatusDTO.setJobStatus("PARTIALLY COMPLETED"); jobStatusDTO.setProcessMessage("Passed"); jobStatusDTO.setTotalSuccessEvents(15l); return jobStatusDTO; } }
测试问题分析与修正方案
当前测试的问题
- 未触发回调执行:直接mock
jdbcTemplate.execute返回true,没有调用PreparedStatementCallback里的doInPreparedStatement方法,导致回调代码块完全没被覆盖。 - 无效代码冗余:
namedTemplate、ResultSet、list、value等对象和insertEvent方法逻辑无关,属于冗余代码,会干扰测试逻辑。 - DTO数据不完整:
createMockDTO未填充jobId、totalEvents、jobStartTime等必填字段,执行回调时会抛出空指针异常。
修正后的测试代码
class AccountUpdateAuditDAOImplTest { @InjectMocks AccountUpdateAuditDAOImpl accountUpdateAuditDAOImpl; @Mock JdbcTemplate jdbcTemplate; @Mock PreparedStatement preparedStatement; @Captor ArgumentCaptor<PreparedStatementCallback<Boolean>> callbackCaptor; @BeforeEach void setup() { MockitoAnnotations.openMocks(this); } @Test void testInsertAccountUpdateEvent() throws AccountUpdateException, SQLException { // 1. 准备完整的测试DTO,避免空指针 JobStatusDTO dto = createCompleteMockDTO(); // 2. mock jdbcTemplate.execute并捕获回调对象 when(jdbcTemplate.execute(anyString(), callbackCaptor.capture())).thenReturn(true); // 3. 调用目标方法 assertDoesNotThrow(() -> accountUpdateAuditDAOImpl.insertEvent(dto)); // 4. 获取捕获的回调,手动执行doInPreparedStatement覆盖代码块 PreparedStatementCallback<Boolean> callback = callbackCaptor.getValue(); callback.doInPreparedStatement(preparedStatement); // 5. 验证PreparedStatement的set方法是否按预期调用 verify(preparedStatement).setLong(1, dto.getJobId()); verify(preparedStatement).setString(2, dto.getFileName()); verify(preparedStatement).setString(3, dto.getJobStatus()); verify(preparedStatement).setLong(4, dto.getTotalEvents()); verify(preparedStatement).setLong(5, dto.getTotalSuccessEvents()); verify(preparedStatement).setLong(6, dto.getTotalFailedEvents()); verify(preparedStatement).setTimestamp(7, new Timestamp(dto.getJobStartTime().getTime())); verify(preparedStatement).setTimestamp(8, new Timestamp(dto.getJobEndTime().getTime())); verify(preparedStatement).setString(9, accountUpdateAuditDAOImpl.processErrorMessage(dto)); verify(preparedStatement).execute(); } @Test void testInsertAccountUpdateEvent_DataAccessException() { JobStatusDTO dto = createCompleteMockDTO(); // mock抛出DataAccessException,覆盖第一个catch块 when(jdbcTemplate.execute(anyString(), any(PreparedStatementCallback.class))) .thenThrow(new DataAccessResourceFailureException("DB连接失败")); AccountUpdateException exception = assertThrows(AccountUpdateException.class, () -> accountUpdateAuditDAOImpl.insertEvent(dto)); assertEquals(Error.ERR_080.getErrorMessage(), exception.getMessage()); } @Test void testInsertAccountUpdateEvent_GeneralException() { JobStatusDTO dto = createCompleteMockDTO(); // mock抛出普通Exception,覆盖第二个catch块 doThrow(new RuntimeException("未知异常")) .when(jdbcTemplate).execute(anyString(), any(PreparedStatementCallback.class)); AccountUpdateException exception = assertThrows(AccountUpdateException.class, () -> accountUpdateAuditDAOImpl.insertEvent(dto)); assertEquals(Error.ERR_081.getErrorMessage(), exception.getMessage()); } // 生成完整的测试DTO,填充所有必要字段 private JobStatusDTO createCompleteMockDTO() { JobStatusDTO jobStatusDTO = new JobStatusDTO(); jobStatusDTO.setJobId(1L); jobStatusDTO.setFileName("rtau_pulse_20220624104610853.txt"); jobStatusDTO.setJobStatus("PARTIALLY COMPLETED"); jobStatusDTO.setProcessMessage("Passed"); jobStatusDTO.setTotalEvents(20L); jobStatusDTO.setTotalSuccessEvents(15L); jobStatusDTO.setTotalFailedEvents(5L); jobStatusDTO.setJobStartTime(new Date()); jobStatusDTO.setJobEndTime(new Date()); return jobStatusDTO; } }
关键优化点
- 通过
ArgumentCaptor捕获PreparedStatementCallback对象,手动调用doInPreparedStatement方法,确保回调代码块被覆盖。 - 补充异常场景测试,覆盖两个catch分支,进一步提升测试覆盖率。
- 删除所有冗余代码,简化测试逻辑,只保留和
insertEvent相关的mock和验证。 - 完善DTO数据填充,避免空指针异常导致测试失败。
内容的提问来源于stack exchange,提问作者Robin kumar
相关产品推荐
相关产品推荐

