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

JUnit Mockito测试遇No Statement specified异常,如何mock flush()

问题描述

在使用JUnit和Mockito进行测试时,调用batch.flush()触发了java.lang.IllegalArgumentException: No Statement specified异常,请问该如何正确mockbatch.flush()方法?


DAO类代码

class DataDao{
    public void updateStatus(List<DataVO> EvntLst, String Ind, String MessageID) {
        try{
            BatchSqlUpdate batch = new BatchSqlUpdate(getDataSource(), UPDATE_TABLE_DATA);
            batch.declareParameter(new SqlParameter(IND, Types.VARCHAR));
            batch.declareParameter(new SqlParameter(ID, Types.BIGINT));
            batch.declareParameter(new SqlParameter(RESID, Types.BIGINT));
            batch.declareParameter(new SqlParameter(SMRYID,Types.BIGINT));
            for (DataVO data: EvntLst){
                Object[] procInd = new Object[4];
                procInd[0] = Ind;
                procInd[1] = data.getId();
                procInd[2] = data.getResourceId();
                procInd[3] = data.getSummaryID();
                batch.update(procInd);
            }
            batch.setBatchSize(EvntLst.size());
            batch.flush();
        }catch(Exception exe){
            throw new Exception(uniqueMessageID + "::更新事件状态时出错");
        }
    }
}

测试类代码

@SpringBootTest(classes = DataDAOTest.class)
class DataDaoTest{
    @Mock
    Connection connection;

    @Mock
    PreparedStatementCreator psc;

    @Mock
    PreparedStatement statement;
    
    @InjectMocks
    DataDAO dataDAO;

    @Mock
    JdbcTemplate jdbcTemplate;
    
    @Test
    public void updateStatus() throws SQLException {
        List<DataVO> EvntLst = new ArrayList<>();
        BatchSqlUpdate batch = mock(BatchSqlUpdate.class);
        Mockito.when(jdbcTemplate.getDataSource()).thenReturn(dataSource);
        Mockito.when(dataSource.getConnection()).thenReturn(connection);
        Mockito.when(psc.createPreparedStatement(connection)).thenReturn(statement);
        batch.setDataSource(dataSource);
        batch.setSql(UPDATE_TABLE_DATA);
        batch.setBatchSize(1);
        String Ind = "Y";
        String MessageID = "xyz";
        DataVO data = new DataVO(1234L, 543L, 567L, "yyyy-mm-dd hh:mm:ss", "123l", "abc", "yyyy-mm-dd hh:mm:ss", "N");
        EvntLst.add(data);
        Object[] procInd = new Object[4];
        procInd[0] = Ind;
        procInd[1] = data.getId();
        procInd[2] = data.getResourceId();
        procInd[3] = data.getSummaryID();
        Mockito.when(batch.update(eq(procInd))).thenReturn(1);
        dataDAO.updateStatus(EvntLst, Ind, MessageID);
    }
}

报错堆栈

java.lang.IllegalArgumentException: No Statement specified
at org.springframework.util.Assert.notNull(Assert.java:201) ~[spring-core-5.3.20.jar:5.3.20]
at org.springframework.jdbc.datasource.DataSourceUtils.applyTimeout(DataSourceUtils.java:334) ~[spring-jdbc-5.3.20.jar:5.3.20]
at org.springframework.jdbc.core.JdbcTemplate.applyStatementSettings(JdbcTemplate.java:1470) ~[spring-jdbc-5.3.20.jar:5.3.20]
at org.springframework.jdbc.core.JdbcTemplate.execute(JdbcTemplate.java:650) ~[spring-jdbc-5.3.20.jar:5.3.20]
at org.springframework.jdbc.core.JdbcTemplate.execute(JdbcTemplate.java:691) ~[spring-jdbc-5.3.20.jar:5.3.20]
at org.springframework.jdbc.core.JdbcTemplate.batchUpdate(JdbcTemplate.java:1034) ~[spring-jdbc-5.3.20.jar:5.3.20]
at org.springframework.jdbc.object.BatchSqlUpdate.flush(BatchSqlUpdate.java:185) ~[spring-jdbc-5.3.20.jar:5.3.20]
at com.uprr.mdd.event.ehms_ds_interchange.process.dao.impl.DataDAO.updateStatus(DataDAO.java:98) [classes/:?]
at com.uprr.mdd.event.ehms_ds_interchange.process.dao.DataDAOTest.updateStatus(DataDAOTest.java:129)

问题原因

测试类中mock的BatchSqlUpdate对象完全没被用到——DataDao的updateStatus方法内部是直接new了一个真实的BatchSqlUpdate实例,这个真实实例缺少必要的Statement配置,执行flush时就会抛出异常。


解决方案

方案1:重构DAO类,抽离BatchSqlUpdate创建逻辑(推荐)

把BatchSqlUpdate的创建逻辑抽成可替换的方法,测试时用Mockito的spy替换该方法返回mock对象:

修改后的DAO类

class DataDao{
    private final DataSource dataSource;
    private final String updateSql;

    // 构造函数注入依赖
    public DataDao(DataSource dataSource, @Value("${sql.update-table-data}") String updateSql) {
        this.dataSource = dataSource;
        this.updateSql = updateSql;
    }

    // 抽离BatchSqlUpdate创建逻辑,供测试替换
    protected BatchSqlUpdate createBatchSqlUpdate() {
        BatchSqlUpdate batch = new BatchSqlUpdate(dataSource, updateSql);
        batch.declareParameter(new SqlParameter(IND, Types.VARCHAR));
        batch.declareParameter(new SqlParameter(ID, Types.BIGINT));
        batch.declareParameter(new SqlParameter(RESID, Types.BIGINT));
        batch.declareParameter(new SqlParameter(SMRYID,Types.BIGINT));
        return batch;
    }

    public void updateStatus(List<DataVO> EvntLst, String Ind, String MessageID) {
        try{
            BatchSqlUpdate batch = createBatchSqlUpdate();
            for (DataVO data: EvntLst){
                Object[] procInd = new Object[4];
                procInd[0] = Ind;
                procInd[1] = data.getId();
                procInd[2] = data.getResourceId();
                procInd[3] = data.getSummaryID();
                batch.update(procInd);
            }
            batch.setBatchSize(EvntLst.size());
            batch.flush();
        }catch(Exception exe){
            throw new Exception(uniqueMessageID + "::更新事件状态时出错");
        }
    }
}

修改后的测试类

@SpringBootTest
class DataDaoTest{
    @InjectMocks
    @Spy // 用spy包装真实DAO对象,以便替换特定方法
    DataDAO dataDAO;

    @Mock
    BatchSqlUpdate mockBatch;

    @Test
    public void updateStatus() throws SQLException {
        List<DataVO> EvntLst = new ArrayList<>();
        String Ind = "Y";
        String MessageID = "xyz";
        DataVO data = new DataVO(1234L, 543L, 567L, "yyyy-mm-dd hh:mm:ss", "123l", "abc", "yyyy-mm-dd hh:mm:ss", "N");
        EvntLst.add(data);
        
        // 让spy对象的createBatchSqlUpdate方法返回mock对象
        Mockito.doReturn(mockBatch).when(dataDAO).createBatchSqlUpdate();
        
        // 执行测试
        dataDAO.updateStatus(EvntLst, Ind, MessageID);
        
        // 验证方法调用
        Object[] expectedParams = new Object[]{Ind, data.getId(), data.getResourceId(), data.getSummaryID()};
        Mockito.verify(mockBatch).update(expectedParams);
        Mockito.verify(mockBatch).flush();
    }
}

方案2:用PowerMock mock构造函数(不推荐,侵入性强)

如果不想修改DAO代码,可以用PowerMock拦截BatchSqlUpdate的构造函数,让new出来的实例是mock对象:

测试类代码

@RunWith(PowerMockRunner.class)
@PrepareForTest(DataDao.class) // 指定要mock构造函数的类
class DataDaoTest{
    @InjectMocks
    DataDAO dataDAO;

    @Mock
    BatchSqlUpdate mockBatch;

    @Test
    public void updateStatus() throws SQLException {
        List<DataVO> EvntLst = new ArrayList<>();
        String Ind = "Y";
        String MessageID = "xyz";
        DataVO data = new DataVO(1234L, 543L, 567L, "yyyy-mm-dd hh:mm:ss", "123l", "abc", "yyyy-mm-dd hh:mm:ss", "N");
        EvntLst.add(data);
        
        // mock BatchSqlUpdate的构造函数,返回mock对象
        PowerMockito.whenNew(BatchSqlUpdate.class)
            .withAnyArguments()
            .thenReturn(mockBatch);
        
        // 执行测试
        dataDAO.updateStatus(EvntLst, Ind, MessageID);
        
        // 验证方法调用
        Object[] expectedParams = new Object[]{Ind, data.getId(), data.getResourceId(), data.getSummaryID()};
        Mockito.verify(mockBatch).update(expectedParams);
        Mockito.verify(mockBatch).flush();
    }
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.18 23:15:43