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

Mockito为何未在数据访问调用中使用我的模拟对象?

问题分析与解决思路

核心问题

你创建的mockDbCache完全没被被测代码用到——因为CartonRoutingProcessor.process()方法里直接new了真实的DbCache实例,而非使用你配置的mock对象。所有针对mockDbCache的Stub配置(比如doReturn(mockDbSql).when(mockDbCache).getDbSql(...))对这个真实实例完全无效,导致后续调用的都是真实DbSql的未初始化方法,最终抛出NullPointerException。

这也对应了你观察到的三个现象:

  • 调试时对象是普通java.sql.*类型:因为用的是真实实例,不是Mockito生成的代理类
  • 步入看到真实DbSql代码:真实实例执行的是自身逻辑,而非Mockito的代理逻辑
  • executeQuery()抛NPE:真实DbSql未完成初始化,调用方法时触发空指针

解决方案

方案1:修改业务代码,支持依赖注入(推荐)

把DbCache的创建逻辑从方法内部移到外部,通过构造方法或参数传入,让测试可以注入mock对象:

修改CartonRoutingProcessor代码

public class CartonRoutingProcessor {
    private final Supplier<DbCache> dbCacheSupplier;

    // 通过构造方法注入DbCache的提供者(支持每次调用生成新实例)
    public CartonRoutingProcessor(Supplier<DbCache> dbCacheSupplier) {
        this.dbCacheSupplier = dbCacheSupplier;
    }

    // 提供默认构造方法,兼容原有调用逻辑
    public CartonRoutingProcessor() {
        this(DbCache::new);
    }

    public boolean process(DbConnect conn, HostMessage msg) throws SQLException {
        try(DbCache dbc = dbCacheSupplier.get()) {
            DbSql mySql = dbc.getDbSql(conn, String.format("select id from routes"));
            ResultSet rs = mySql.executeQuery();
            String routeType = rs.next() ? rs.getString("id") : null;
            // ... 后续业务逻辑
        }
        // ...
    }
}

修改测试代码

@Test
void myTest() {
    String errMsg = "something";

    // 配置mock行为
    doReturn(mockDbSql).when(mockDbCache).getDbSql(any(), anyString());
    doReturn(mockResultSet).when(mockDbSql).executeQuery();
    doReturn(false).when(mockResultSet).next();

    // 传入mockDbCache的提供者
    CartonRoutingProcessor processor = new CartonRoutingProcessor(() -> mockDbCache);
    // 验证行为或异常
    SQLException exception = Assertions.assertThrows(SQLException.class, () -> {
        processor.process(mockDbConnect, mockHostMessage);
    });
    Assertions.assertTrue(exception.getMessage().contains(errMsg),
            "Phrase not found in SqlException message: " + errMsg);
}

方案2:用PowerMock拦截new操作(不修改源码时使用)

如果无法修改业务代码,可以用PowerMock拦截new DbCache()的调用,强制返回你的mock对象:

测试代码调整

@RunWith(PowerMockRunner.class)
@PrepareForTest(CartonRoutingProcessor.class) // 指定要拦截new操作的类
public class CartonRoutingProcessorTest {
    HostMessage mockHostMessage;
    DbConnect mockDbConnect;
    DbCache mockDbCache;
    DbSql mockDbSql;
    ResultSet mockResultSet;

    @BeforeEach
    void setUp() {
        MockitoAnnotations.openMocks(this);
        mockHostMessage = Mockito.mock(HostMessage.class);
        mockDbConnect = Mockito.mock(DbConnect.class);
        mockDbCache = Mockito.mock(DbCache.class);
        mockDbSql = Mockito.mock(DbSql.class);
        mockResultSet = Mockito.mock(ResultSet.class);
    }

    @Test
    void myTest() throws Exception {
        String errMsg = "something";

        // 拦截new DbCache(),返回mock对象
        PowerMockito.whenNew(DbCache.class).withNoArguments().thenReturn(mockDbCache);

        // 配置mock行为
        doReturn(mockDbSql).when(mockDbCache).getDbSql(any(), anyString());
        doReturn(mockResultSet).when(mockDbSql).executeQuery();
        doReturn(false).when(mockResultSet).next();

        // 执行测试并验证
        SQLException exception = Assertions.assertThrows(SQLException.class, () -> {
            new CartonRoutingProcessor().process(mockDbConnect, mockHostMessage);
        });
        Assertions.assertTrue(exception.getMessage().contains(errMsg));
    }
}

注意:PowerMock通过修改字节码实现拦截,可能存在兼容性问题,优先推荐方案1。

额外优化建议

测试中的异常捕获逻辑可以用Assertions.assertThrows替代手动try-catch,代码更简洁且符合JUnit规范,如上述示例所示。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.16 21:15:47