I have jdbcTemplate code, on which I am trying to write a unit test case.
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:
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.
Advertisement
Answer
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.