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

如何在JUnit 5 + Mockito 5中Mock DataSource?遗留DAO测试遇阻

问题分析

你的DAO类存在两个关键测试阻碍:

  1. ds是静态字段,且getData()每次调用都会执行getDataSource(),重新创建XmlBeanFactory并覆盖ds的值
  2. 之前的Mock用法存在逻辑错误:
    • MockedConstruction的配置时机不对,导致获取mock实例时列表为空抛出索引越界
    • 反射注入时误操作了实例字段,而非静态字段,且后续被getDataSource()覆盖
解决方案

方案1:正确拦截XmlBeanFactory的构造与行为

利用Mockito 5的MockedConstruction在XmlBeanFactory实例创建时,直接配置getBean()返回mock的DataSource,确保getDataSource()能拿到预期的mock对象。

import org.junit.jupiter.api.Test;
import org.mockito.MockedConstruction;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.PreparedStatement;
import static org.mockito.Mockito.*;

class DAOTest {

    @Test
    void testGetData() throws Exception {
        // 准备Mock组件
        DataSource mockDs = mock(DataSource.class);
        Connection mockConn = mock(Connection.class);
        PreparedStatement mockPst = mock(PreparedStatement.class);

        // 配置Mock行为
        when(mockDs.getConnection()).thenReturn(mockConn);
        when(mockConn.prepareStatement(anyString())).thenReturn(mockPst);

        // 拦截XmlBeanFactory构造,实时配置getBean返回值
        try (MockedConstruction<XmlBeanFactory> factoryMock = mockConstruction(XmlBeanFactory.class,
                (mockFactory, context) -> {
                    when(mockFactory.getBean("dataSourceString")).thenReturn(mockDs);
                })) {
            // 调用目标方法
            DAO.getData("some_input_data");

            // 验证行为是否符合预期
            verify(factoryMock.constructed().get(0)).getBean("dataSourceString");
            verify(mockDs).getConnection();
            verify(mockConn).prepareStatement("select * from sometable");
        }
    }
}

方案2:Mock静态方法+反射设置静态字段

通过Mockito 5的mockStatic拦截getDataSource()的执行逻辑,避免它覆盖我们注入的mock DataSource,再通过反射直接设置静态字段ds。

import org.junit.jupiter.api.Test;
import org.mockito.MockedStatic;
import java.lang.reflect.Field;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.PreparedStatement;
import static org.mockito.Mockito.*;

class DAOTest {

    @Test
    void testGetData() throws Exception {
        // 准备Mock组件
        DataSource mockDs = mock(DataSource.class);
        Connection mockConn = mock(Connection.class);
        PreparedStatement mockPst = mock(PreparedStatement.class);

        // 配置Mock行为
        when(mockDs.getConnection()).thenReturn(mockConn);
        when(mockConn.prepareStatement(anyString())).thenReturn(mockPst);

        // 拦截DAO的静态方法,阻止getDataSource()执行原有逻辑
        try (MockedStatic<DAO> daoStaticMock = mockStatic(DAO.class)) {
            // 反射设置静态字段ds(静态字段用null作为set方法的实例参数)
            Field dsField = DAO.class.getDeclaredField("ds");
            dsField.setAccessible(true);
            dsField.set(null, mockDs);

            // 调用目标方法
            DAO.getData("some_input_data");

            // 验证行为
            verify(mockDs).getConnection();
            verify(mockConn).prepareStatement("select * from sometable");
            // 可选:验证getDataSource()未被执行
            daoStaticMock.verify(DAO::getDataSource, never());
        }
    }
}
失败原因复盘
  • 尝试1失败:你在mockConstruction创建后才去获取constructed().get(0),但此时XmlBeanFactory还未被getDataSource()实例化,导致列表为空抛出索引越界。正确做法是在构造回调中实时配置mock行为。
  • 尝试2失败:你给dao实例设置字段,但ds是静态字段,属于类而非实例,因此parentService.set(dao, mockDataSource)无效;且后续getDataSource()执行后会重新覆盖ds的值为null。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.29 04:32:41