如何对使用ResultSet和PreparedStatement的方法进行单元测试
问题原因
你触发空指针的核心原因是DataBaseService内部依赖的connection对象没有被mock和注入,你当前只mock了ResultSet,甚至直接mock了DataBaseService本身再调用真实方法,服务内部用到的connection、PreparedStatement都是空值,调用connection.prepareStatement时自然会抛出空指针异常。
正确的单元测试编写逻辑
- 不要直接mock你要测试的业务类本身,只需要mock它的所有外部依赖即可,你要验证的就是业务类的真实代码逻辑
- 梳理清楚方法内部的调用链路,按顺序给每个mock依赖定义对应入参的返回值,不要遗漏任何一步调用
- 涉及结果集循环的场景,要匹配真实的迭代逻辑模拟
next()方法的返回值
修正后的测试代码示例
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.mockito.Mockito; import org.springframework.test.util.ReflectionTestUtils; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotEquals; import static org.mockito.Mockito.*; public class DataBaseServiceTest { // 要测试的是真实业务类,不要mock private DataBaseService dataBaseService; // mock所有外部依赖对象 private Connection connection; private PreparedStatement preparedStatement; private ResultSet resultSet; @BeforeEach public void initMock() { connection = mock(Connection.class); preparedStatement = mock(PreparedStatement.class); resultSet = mock(ResultSet.class); dataBaseService = new DataBaseService(); // 注入mock的connection到业务类,根据你实际的注入方式调整 // 如果有构造方法注入可以直接new DataBaseService(connection) // 如果是私有字段无setter用反射注入: ReflectionTestUtils.setField(dataBaseService, "connection", connection); } @Test public void getLastIdIsCorrect() throws SQLException { // given int expectedId = 1; String matchQuery = "select 3537340;"; // 按方法调用顺序定义mock返回 when(connection.prepareStatement(matchQuery)).thenReturn(preparedStatement); when(preparedStatement.executeQuery()).thenReturn(resultSet); // 匹配while循环逻辑:第一次next返回true读取数据,第二次返回false结束循环 when(resultSet.next()).thenReturn(true).thenReturn(false); when(resultSet.getInt("3537340")).thenReturn(expectedId); // when int result = dataBaseService.getLastId(); // then assertEquals(expectedId, result); assertNotEquals(101, result); // 可选:验证依赖调用符合预期,避免逻辑写错但返回值刚好匹配的情况 verify(connection).prepareStatement(matchQuery); verify(preparedStatement).executeQuery(); verify(resultSet, times(2)).next(); verify(resultSet).getInt("3537340"); } }
同类数据库操作方法通用测试方案
所有涉及JDBC操作的方法都可以套用这个逻辑:
- 先找出方法依赖的所有外部资源:Connection、PreparedStatement、ResultSet,全部做mock
- 按照方法内部的执行顺序,依次定义每个mock对象的入参匹配规则和返回值
- 针对分支场景分别写测试用例,比如结果集为空的场景、SQL执行抛异常的场景都可以单独覆盖
- 必要时添加
verify验证调用次数、入参是否符合预期,提高测试准确性
内容的提问来源于stack exchange,提问作者Lulex97
相关产品推荐
相关产品推荐

