如何Mock动态数据源测试searchQuery方法?解决空指针异常
解决ValidationData.searchQuery方法的Mockito测试方案
核心思路
通过Mock替代真实的数据库连接链路:从createConnection返回的DataSource,到Connection、PreparedStatement、ResultSet,全程Mock,完全脱离真实数据库依赖。
具体实现步骤
1. 确保createConnection方法可被Stub
如果createConnection是私有方法,优先考虑重构为protected(方便测试时Stub);若无法重构,可借助PowerMock实现私有方法Mock。如果是public/protected方法,直接用Mockito处理即可。
2. 逐层Mock数据库相关对象
从数据源到结果集,逐层Stub调用逻辑,模拟真实数据库的返回行为:
- Mock
DataSource,让createConnection返回该Mock实例 - Mock
Connection,让DataSource的getConnection()返回此Mock连接 - Mock
PreparedStatement,让Connection的prepareStatement()返回此Mock语句 - Mock
ResultSet,让PreparedStatement的executeQuery()返回预设数据的Mock结果集
3. 测试代码示例
假设目标类结构如下:
public class ValidationData { public String searchQuery(String sql) throws SQLException { DataSource dataSource = createConnection(); try (Connection conn = dataSource.getConnection(); PreparedStatement pstmt = conn.prepareStatement(sql); ResultSet rs = pstmt.executeQuery()) { if (rs.next()) { return rs.getString("target_col"); } return null; } } protected DataSource createConnection() { // 真实环境中获取动态数据源的逻辑 return null; } }
对应的测试类:
import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; import javax.sql.DataSource; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import static org.junit.Assert.assertEquals; import static org.mockito.Mockito.*; @RunWith(MockitoJUnitRunner.class) public class ValidationDataTest { @Mock private DataSource mockDataSource; @Mock private Connection mockConn; @Mock private PreparedStatement mockPstmt; @Mock private ResultSet mockRs; @Test public void testSearchQuery() throws Exception { // 1. 创建目标类实例并Stub createConnection方法 ValidationData validationData = spy(new ValidationData()); doReturn(mockDataSource).when(validationData).createConnection(); // 2. 逐层Stub数据库调用链 when(mockDataSource.getConnection()).thenReturn(mockConn); when(mockConn.prepareStatement(anyString())).thenReturn(mockPstmt); when(mockPstmt.executeQuery()).thenReturn(mockRs); // 3. 预设ResultSet返回数据 when(mockRs.next()).thenReturn(true); when(mockRs.getString("target_col")).thenReturn("expected_result"); // 4. 执行测试方法 String result = validationData.searchQuery("SELECT target_col FROM test_table"); // 5. 断言结果正确性 assertEquals("expected_result", result); // 6. 验证调用次数(可选,确保逻辑执行符合预期) verify(mockDataSource, times(1)).getConnection(); verify(mockConn, times(1)).prepareStatement(anyString()); } }
4. 私有方法的特殊处理
如果createConnection是私有方法,需引入PowerMock配合Mockito:
import org.junit.Test; import org.junit.runner.RunWith; import org.powermock.api.mockito.PowerMockito; import org.powermock.core.classloader.annotations.PrepareForTest; import org.powermock.modules.junit4.PowerMockRunner; import javax.sql.DataSource; import static org.junit.Assert.assertEquals; @RunWith(PowerMockRunner.class) @PrepareForTest(ValidationData.class) public class ValidationDataTest { @Test public void testSearchQueryWithPrivateMethod() throws Exception { ValidationData validationData = PowerMockito.spy(new ValidationData()); DataSource mockDataSource = mock(DataSource); // Mock私有createConnection方法 PowerMockito.doReturn(mockDataSource).when(validationData, "createConnection"); // 后续Mock Connection、PreparedStatement、ResultSet逻辑同上述示例 // ... String result = validationData.searchQuery("SELECT * FROM test_table"); assertEquals("expected_result", result); } }
5. 长期优化方案:重构解耦
如果频繁遇到此类测试问题,建议重构代码,将DataSource作为依赖通过构造器注入:
public class ValidationData { private final DataSource dataSource; public ValidationData(DataSource dataSource) { this.dataSource = dataSource; } public String searchQuery(String sql) throws SQLException { try (Connection conn = dataSource.getConnection(); PreparedStatement pstmt = conn.prepareStatement(sql); ResultSet rs = pstmt.executeQuery()) { // 业务逻辑... } } }
这种情况下测试更简洁,直接Mock DataSource注入即可,无需处理createConnection方法。
关键注意事项
- 所有数据库相关对象必须全程Mock,避免触发真实连接逻辑
- 针对try-with-resources语法,Mockito会自动处理资源关闭的验证,无需额外配置
- 若需模拟多行结果集,可通过
when(mockRs.next()).thenReturn(true, true, false)实现
内容的提问来源于stack exchange,提问作者Alex
相关产品推荐
相关产品推荐

