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
相关产品推荐
相关产品推荐

