写一个用于jdbcTemplate.batchUpdate()方法的单元测试。

huangapple go评论101阅读模式
英文:

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");
}

请注意,我已经按照您的要求,只返回了翻译好的部分,没有包括其他内容。如果您需要进一步的帮助,请随时提问。

英文:

I have jdbcTemplate code, on which I am trying to write a unit test case.

<br/>

     public void updateData(List&lt;Student&gt; students, String status){
        try{jdbcTemplate.batchUpdate(&quot;update query&quot;, 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){}
    
 }

But the problem is I am unable to cover the full code. I am able to cover till:<br/>

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

Test code snippet

@Test
public void testMe(){
List&lt;Student&gt; students = new ArrayList&lt;&gt;();
 mockedObject.updateData(students ,&quot;success&quot;);

}

Please help.

答案1

得分: 3

在这里,困难在于包含您想要测试的主要逻辑的 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());            
                 }
          });
     }
}

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

// 注入它
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

例如,对于第二种情况:

// 模拟工厂返回
BatchPreparedStatementSetter batchPreparedStatementSetterDummyMock = Mockito.mock(BatchPreparedStatementSetter.class);
Mockito.when(batchPreparedStatementFactoryMock.ofStudentsBatchPreparedStatementSetter(students, status))
  .thenReturn(batchPreparedStatementSetterDummyMock);

// 调用要测试的方法
updateData(students, status);

// 验证我们是否使用预期的参数调用了工厂
Mockito.verify(jdbcTemplateMock)
        .batchUpdate("update query", batchPreparedStatementSetterDummyMock);

就个人而言,我不太喜欢这种过于精细的模拟单元测试。我更倾向于使用 @DataJpaTest 或更全面的集成测试来断言与 JDBC/JPA 相关的事物。

英文:

Here the difficulty is that the new BatchPreparedStatementSetter(){ ...} instance that contains the main logic that you want to test is a implementation detail of the updateData() method. It is defined only inside the tested method.
To solve that you have two classic approaches :

  • favor a test slice with @DataJpaTest (that is finally a partial integration test) that would be simpler since you will be able to test side effect and also more helpful as you assert the state in the DB and not the statements in your code.
  • Extract the BatchPreparedStatementSetter instance creation in a factory.
    In that way you could capture it inside your unit test.

For example :

@Service
class BatchPreparedStatementFactory{

   public BatchPreparedStatementSetter ofStudentsBatchPreparedStatementSetter(List&lt;Student&gt; 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());            
                 }
          });
     }
}

And use it now in your original code :

 // inject it
 BatchPreparedStatementFactory batchPreparedStatementFactory;

 public void updateData(List&lt;Student&gt; students, String status){
    try{jdbcTemplate.batchUpdate(&quot;update query&quot;, batchPreparedStatementFactory.ofStudentsBatchPreparedStatementSetter(students, status );
    }catch(Exception ex){}    
  }

Now you have two components and so two tests :

  • BatchPreparedStatementFactoryTest (without mocking) which tests getBatchSize() and setValues(). That is very straight.
  • your initial test (with mocking) that asserts that the jdbcTemplate.batchUpdate() is invoked with expected parameters, particularly the instance returned by BatchPreparedStatementFactory.ofStudentsBatchPreparedStatementSetter(...).
    To do that assertion, you should define several mocks :
    jdbcTemplate, BatchPreparedStatementFactory and BatchPreparedStatementSetter.

For example for the second case :

// 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(&quot;update query&quot;, batchPreparedStatementSetterDummyMock);

Personally I am not a big fan of that kind of unit tests with too fine mocking. I would stick to @DataJpaTest or more global integration tests to assert things related to JDBC/JPA.

答案2

得分: 0

如@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));
}
英文:

As @davidxxx answered, it is good solution to refactor your code by creating a factory. If you do not wish to create a factory, you can use below solution to unit test the logic written inside batchUpdate call.

import static org.mockito.Mockito.*;

@Test
public void testJDBCBatchUpdate() {
    String expectedSQL = &quot;Select * from TableName&quot;;
    doAnswer(invocationOnMock -&gt; {
 
        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&lt;Datum&gt; data = service.getData();
    
	verify(jdbcTemplate, times(1)).batchUpdate(anyString(),any(BatchPreparedStatementSetter.class)); 
}

huangapple
  • 本文由 发表于 2020年7月25日 14:14:43
  • 转载请务必保留本文链接:https://go.coder-hub.com/63085027.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定