You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用Mockito测试CallableStatementCreator返回CallableStatement为null问题

问题根因

1. 业务代码逻辑错误

你不应该在CallableStatementCreator的实现方法中使用try-with-resources包裹CallableStatement对象。
Spring JdbcTemplate的设计规范是由框架自身管理数据库连接、Statement等资源的生命周期,你在方法内部用try-with-resources包裹CallableStatement,会导致方法return之后,try块执行完毕自动触发cs.close(),最终返回的是已经被关闭的无效Statement对象,这也是SpotBugs报ODR_OPEN_DATABASE_RESOURCE错误的直接原因。

2. 测试返回null的原因

  • 初始化顺序错误:你的setup方法中先创建了cut的spy对象,再调用MockitoAnnotations.openMocks(this),后者执行时会把标注了@InjectMocks的cut字段重新实例化为普通的实现类对象,直接覆盖了你之前创建的spy实例,导致Mock逻辑不生效。
  • 精确字符串匹配未命中:你用固定SQL字符串作为prepareCall方法的入桩参数时,如果字符串存在不可见字符、大小写差异、空格差异,都会导致匹配失败,此时Mockito会返回默认值null,这也是你改用Mockito.anyString()就能正常运行的原因。

修复方案

业务代码修改

去掉try-with-resources包装,直接返回创建好的CallableStatement,资源交由JdbcTemplate管理即可:

@Override
@SuppressFBWarnings(value="RCN_REDUNDANT_NULLCHECK_WOULD_HAVE_BEEN_A_NPE",justification = "try with resource false positive issue with java 11")
public CallableStatement createCallableStatement(Connection con) throws SQLException {
    CallableStatement cs = con.prepareCall("{call Employee.CHANGE_TO_WARM_STORE(?,?)}");
    cs.setInt(1, 2);
    cs.registerOutParameter(2, Types.INTEGER);
    return cs;
}

测试代码修改

  1. 调整初始化顺序,先初始化Mock再创建spy对象
  2. 将SQL提取为全局常量,入桩和业务代码都引用同一个常量,既可以保证精确匹配成功,也不会触发SpotBugs的SQL_INJECTION_JDBC报错
    修改后测试代码参考:
import static org.mockito.Mockito.doReturn;
import static org.testng.Assert.assertNotNull;

import java.sql.CallableStatement;
import java.sql.Connection;
import java.sql.SQLException;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;

public class TimEventWarmStoreCallableStatementTest {
  // 定义SQL常量,业务代码也引用该常量
  private static final String CALL_SQL = "{call Employee.CHANGE_TO_WARM_STORE(?,?)}";

  @InjectMocks
  private TimeEventWarmStoreCallableStatement cut;

  @Mock
  private Connection con;

  @Mock
  private CallableStatement callableStatement;

  @BeforeMethod
  public void setup() {
    // 先初始化Mock
    MockitoAnnotations.openMocks(this);
    // 再包装spy
    cut = Mockito.spy(new TimeEventWarmStoreCallableStatement());
  }

  @Test
  public void createCallableStatementTest() throws SQLException {
    doReturn(callableStatement).when(con).prepareCall(CALL_SQL);
    CallableStatement cst = cut.createCallableStatement(con);
    assertNotNull(cst);
  }
}

内容的提问来源于stack exchange,提问作者henrycharles

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.19 23:45:03