为 jdbcTemplate.batchUpdate() 方法编写单元测试

Write unit test for jdbcTemplate.batchUpdate() method

我有 jdbcTemplate 代码,我正在尝试在其上编写单元测试用例。


     public void updateData(List<Student> students, String status){
        try{jdbcTemplate.batchUpdate("update query", new BatchPreparedStatementSetter(){
    
            @Override
            public int getBatchSize()
              return students.size();
            }  
            @Override
            public void setValues(PreparedStatement ps int i){
              Student student = students.get(i);
              ps.setInt(1, student.getRollNo());
              ps.setString(2, student.getName());            
             }
      });
    }catch(Exception ex){}
    
 }

但问题是我无法涵盖完整代码。我可以覆盖到:

try{jdbcTemplate.batchUpdate("update query", new BatchPreparedStatementSetter(){

测试代码片段

@Test
public void testMe(){
List<Student> students = new ArrayList<>();
 mockedObject.updateData(students ,"success");

}

请帮忙。

这里的难点在于new BatchPreparedStatementSetter(){ ...}包含你要测试的主要逻辑的实例是updateData()方法的一个实现细节。它在测试方法中定义
要解决这个问题,您有两种经典方法:

  • 支持带有 @DataJpaTest 的测试片(这最终是部分集成测试),这会更简单,因为您将能够测试副作用,并且在断言数据库中的状态时也更有帮助,并且不是您代码中的语句。
  • 提取工厂中的 BatchPreparedStatementSetter 实例创建。
    通过这种方式,您可以在单元测试中捕获它。

例如:

@Service
class BatchPreparedStatementFactory{

   public BatchPreparedStatementSetter ofStudentsBatchPreparedStatementSetter(List<Student> students, String status){

      return 
      new BatchPreparedStatementSetter(){
        
                @Override
                public int getBatchSize()
                  return students.size();
                }  
                @Override
                public void setValues(PreparedStatement ps int i){
                  Student student = students.get(i);
                  ps.setInt(1, student.getRollNo());
                  ps.setString(2, student.getName());            
                 }
          });
     }
}

现在在您的原始代码中使用它:

 // inject it
 BatchPreparedStatementFactory batchPreparedStatementFactory;

 public void updateData(List<Student> students, String status){
    try{jdbcTemplate.batchUpdate("update query", batchPreparedStatementFactory.ofStudentsBatchPreparedStatementSetter(students, status );
    }catch(Exception ex){}    
  }

现在你有两个组件和两个测试:

  • BatchPreparedStatementFactoryTest(没有模拟)测试 getBatchSize()setValues()。很直。
  • 您的初始测试(带模拟)断言 jdbcTemplate.batchUpdate() 是使用预期参数调用的,特别是 BatchPreparedStatementFactory.ofStudentsBatchPreparedStatementSetter(...).
    返回的实例 要执行该断言,您应该定义几个模拟: jdbcTemplateBatchPreparedStatementFactoryBatchPreparedStatementSetter

例如第二种情况:

// mock the factory return
BatchPreparedStatementSetter batchPreparedStatementSetterDummyMock = Mockito.mock(BatchPreparedStatementSetter.class);
Mockito.when(batchPreparedStatementFactoryMock.ofStudentsBatchPreparedStatementSetter(students, status))
  .thenReturn(batchPreparedStatementSetterDummyMock);

// call the method to test
updateData(students, status);

// verify that we call the factory with the expected params
 Mockito.verify(jdbcTemplateMock)
        .batchUpdate("update query", batchPreparedStatementSetterDummyMock);

就我个人而言,我不是那种带有太精细模拟的单元测试的忠实拥护者。我会坚持 @DataJpaTest 或更多的全局集成测试来断言与 JDBC/JPA.

相关的事情

正如@davidxxx 回答的那样,通过创建工厂来重构代码是一个很好的解决方案。如果您不想创建工厂,可以使用以下解决方案对 batchUpdate 调用中编写的逻辑进行单元测试。

import static org.mockito.Mockito.*;

@Test
public void testJDBCBatchUpdate() {
    String expectedSQL = "Select * from TableName";
    doAnswer(invocationOnMock -> {
 
        String actualSQL =invocationOnMock.getArgumentAt(0, String.class);
        assertEquals(expectedSQL, actualSQL);
        
        PreparedStatement preparedStatementMock=Mockito.mock(PreparedStatement.class);
        BatchPreparedStatementSetter setter =invocationOnMock.getArgumentAt(1, BatchPreparedStatementSetter.class);
        setter.setValues(preparedStatementMock, 0);
        
        verify(preparedStatementMock, times(1)).setObject(anyInt(), anyString());
        
        int batchSize=setter.getBatchSize();
        assertEquals(expectedBatchSize,batchSize);
        
        return null;
    }).when(jdbcTemplate).batchUpdate(anyString(), any(BatchPreparedStatementSetter.class));
    
    List<Datum> data = service.getData();
    
    verify(jdbcTemplate, times(1)).batchUpdate(anyString(),any(BatchPreparedStatementSetter.class)); 
}