英文:
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<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){}
}
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<Student> students = new ArrayList<>();
mockedObject.updateData(students ,"success");
}
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(...)
返回的实例。
为了进行这种断言,您应该定义几个模拟:jdbcTemplate
、BatchPreparedStatementFactory
和BatchPreparedStatementSetter
。
例如,对于第二种情况:
// 模拟工厂返回
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<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());
}
});
}
}
And use it now in your original code :
// inject it
BatchPreparedStatementFactory batchPreparedStatementFactory;
public void updateData(List<Student> students, String status){
try{jdbcTemplate.batchUpdate("update query", batchPreparedStatementFactory.ofStudentsBatchPreparedStatementSetter(students, status );
}catch(Exception ex){}
}
Now you have two components and so two tests :
BatchPreparedStatementFactoryTest
(without mocking) which testsgetBatchSize()
andsetValues()
. That is very straight.- your initial test (with mocking) that asserts that the
jdbcTemplate.batchUpdate()
is invoked with expected parameters, particularly the instance returned byBatchPreparedStatementFactory.ofStudentsBatchPreparedStatementSetter(...)
.
To do that assertion, you should define several mocks :
jdbcTemplate
,BatchPreparedStatementFactory
andBatchPreparedStatementSetter
.
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("update query", 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 = "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));
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论