使用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; }
测试代码修改
- 调整初始化顺序,先初始化Mock再创建spy对象
- 将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
相关产品推荐
相关产品推荐

