不建议直接 mock PreparedStatement,因其是接口且行为依赖 JDBC 驱动和数据库连接;应 mock 上层依赖(如 DataSource)或用 H2 内存库做集成测试,或抽取 DAO 接口进行面向抽象的单元测试。

Java 中对 PreparedStatement 进行单元测试时,**不建议直接 mock 它本身**,因为它是接口,且其行为高度依赖于 JDBC 驱动和数据库连接。真正该 mock 的是它的上层依赖——比如 Connection、DataSource,或更推荐:**mock 数据访问层(如 DAO)的调用逻辑,或使用内存数据库(如 H2)做集成式测试**。
为什么不要直接 mock PreparedStatement?
虽然技术上可以用 Mockito mock PreparedStatement 接口,但会带来以下问题:
- 需要手动 stub 大量方法(
setString()、executeQuery()、getResultSet()、close()等),代码冗长且易错 - 无法验证 SQL 参数绑定是否正确(mock 不反映实际执行逻辑)
- 绕过了 JDBC 调用链,测试失真,容易漏掉空指针、类型不匹配等运行时问题
- 违反“测试行为而非实现”的原则——你真正关心的是 DAO 是否正确执行了 SQL,而不是它怎么调用
setInt(1, 100)
推荐方案一:Mock DataSource + Connection + PreparedStatement(仅限简单场景)
适用于轻量逻辑验证(如确认 SQL 拼写、参数个数),需谨慎使用:
// 使用 Mockito
DataSource dataSource = mock(DataSource.class);
Connection conn = mock(Connection.class);
PreparedStatement ps = mock(PreparedStatement.class);
ResultSet rs = mock(ResultSet.class);
<p>when(dataSource.getConnection()).thenReturn(conn);
when(conn.prepareStatement("SELECT * FROM user WHERE id = ?")).thenReturn(ps);
when(ps.executeQuery()).thenReturn(rs);
when(rs.next()).thenReturn(true).thenReturn(false);
when(rs.getString("name")).thenReturn("Alice");</p><p>// 执行被测 DAO 方法
User user = userDao.findById(123);</p><p>// 验证
verify(ps).setInt(1, 123);
verify(ps).executeQuery();
assertThat(user.getName()).isEqualTo("Alice");
注意:必须严格按实际 SQL 字符串匹配(含空格、大小写),否则 when(...prepareStatement(...)) 不生效;还需手动管理 close() 行为,否则可能资源泄漏。
立即学习“Java免费学习笔记(深入)”;
Java项目代码review工具。分析Git变更+完整调用链路上下文,推断业务需求,进行多维度评分和分类汇总,生成完整PRD文档。包含细粒度Java代码审查清单(Null安全、异常处理、Streams、并发、equals/hashCode、资源管理、API设计、性能、MyBatis/ORM、事务边界、SQL/DD...
推荐方案二:用 H2 内存数据库做真实 SQL 测试(强烈推荐)
零 mock、真实执行、覆盖参数绑定、事务、异常路径,且启动快、可复位:
- 添加 H2 依赖:
com.h2database:h2 - 在测试中初始化内存数据源:
jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1 - 用 Flyway/Liquibase 或纯 JDBC 执行建表语句
- 插入测试数据 → 调用 DAO → 断言结果
示例片段:
@BeforeEach
void setUp() {
JdbcDataSource ds = new JdbcDataSource();
ds.setUrl("jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1");
ds.setUser("sa"); ds.setPassword("");
<pre class="brush:php;toolbar:false;">try (Connection c = ds.getConnection()) {
c.createStatement().execute("CREATE TABLE user(id INT PRIMARY KEY, name VARCHAR(50))");
c.createStatement().execute("INSERT INTO user VALUES (1, 'Tom'), (2, 'Jerry')");
}
userDao = new UserDao(ds); // 注入真实 DataSource}
@Test void findById_returnsUser_whenExists() { User user = userDao.findById(1); assertThat(user.getName()).isEqualTo("Tom"); }
推荐方案三:抽取并 mock DAO 接口(面向抽象编程)
将 JDBC 操作封装在 DAO 接口中,业务层只依赖 DAO 接口。单元测试时直接 mock DAO 方法,完全隔离数据库:
// 定义接口
public interface UserDao {
User findById(int id);
}
<p>// 业务类
public class UserService {
private final UserDao userDao;
public UserService(UserDao userDao) { this.userDao = userDao; }
public String getUserName(int id) {
return userDao.findById(id).getName(); // 无 JDBC 细节
}
}</p><p>// 测试
@Test
void getUserName_returnsName_fromMockedDao() {
UserDao mockDao = mock(UserDao.class);
when(mockDao.findById(999)).thenReturn(new User(999, "Mocked"));</p><pre class="brush:php;toolbar:false;">UserService service = new UserService(mockDao);
assertThat(service.getUserName(999)).isEqualTo("Mocked");}
这种方式测试最轻量、最稳定,适合验证业务逻辑,但需配合方案二(H2)保障 DAO 层自身正确性。

















