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

SpringBoot+Mockito模拟DataSourceTransactionManager遇空指针异常求解

SpringBoot + Mockito 模拟 DataSourceTransactionManager 空指针问题

问题场景

在SpringBoot项目中用Mockito模拟DataSourceTransactionManager时,执行doReturn(status).when(dataSourceTransactionManager).getTransaction(def)触发空指针异常,换成@MockBean注解也无法解决,不清楚Spring加载后事务管理器实例为何为空,求正确的Mock姿势。

测试类原代码

@RunWith(SpringRunner.class)
@SpringBootTest(classes = SpringBootServerApplication.class)
public class MockTxTest {

    @Resource
    private Service service;

    @SpyBean
    private DataSourceTransactionManager dataSourceTransactionManager;

    private static DefaultTransactionDefinition def;
    private static TransactionStatus status;

    static {
        def = new DefaultTransactionDefinition();
        def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
    }

    @Before
    public void setUp() {
        MockitoAnnotations.initMocks(this);
        status = dataSourceTransactionManager.getTransaction(def);
    }

    @Test
    public void test_tx() {
        doReturn(status).when(dataSourceTransactionManager).getTransaction(def);
        // doNothing().when(dataSourceTransactionManager).commit(any());

        Assert.assertTrue(service.method() != 0);
        verify(dataSourceTransactionManager, times(1)).getTransaction(def);
    }
}

业务类原代码

public class Service {
    @Resource
    private DataSourceTransactionManager dataSourceTransactionManager;

    public int method() {
        TransactionDefinition definition = new DefaultTransactionDefinition();
        TransactionStatus status = dataSourceTransactionManager.getTransaction(definition);
        try {
            doThing();
            dataSourceTransactionManager.commit(status);
            return 1;
        } catch (Exception ex) {
            log.error("exception,bo:{} ,ex", bo, ex);
            dataSourceTransactionManager.rollback(status);
            throw new Exception();
        }
    }

    private void doThing() {
        // 业务逻辑
    }
}

问题根源

  1. 初始化冲突:同时使用SpringRunner(管理Spring上下文)和MockitoAnnotations.initMocks(this)(Mockito独立初始化),导致Spring托管的@SpyBean实例被Mockito重置为null,触发空指针。
  2. 提前调用真实方法:@Before中直接调用dataSourceTransactionManager.getTransaction(def),此时SpyBean还未完成Mock配置,会执行真实逻辑,而Spring上下文可能未完全加载,导致真实方法内部抛出空指针。
  3. 参数匹配失效:业务类中创建的TransactionDefinition是新实例,和测试类中的def不是同一个对象,Mockito的严格参数匹配导致Mock不生效,转而调用真实方法引发空指针。

正确解决方案

修正后的测试类

@RunWith(SpringRunner.class)
@SpringBootTest(classes = SpringBootServerApplication.class)
public class MockTxTest {

    @Resource
    private Service service;

    @SpyBean
    private DataSourceTransactionManager dataSourceTransactionManager;

    private TransactionStatus mockStatus;

    @Before
    public void setUp() {
        // 移除MockitoAnnotations.initMocks,SpringRunner已完成Mock/Spy的初始化
        // 直接Mock TransactionStatus,无需调用真实的getTransaction方法
        mockStatus = Mockito.mock(TransactionStatus.class);
    }

    @Test
    public void test_tx() {
        // 使用any()匹配任意TransactionDefinition实例,解决参数不匹配问题
        doReturn(mockStatus).when(dataSourceTransactionManager).getTransaction(any(TransactionDefinition.class));
        // 可选:模拟commit/rollback的空实现,避免真实事务操作
        doNothing().when(dataSourceTransactionManager).commit(any(TransactionStatus.class));
        doNothing().when(dataSourceTransactionManager).rollback(any(TransactionStatus.class));

        Assert.assertTrue(service.method() != 0);
        // 验证getTransaction方法被调用一次
        verify(dataSourceTransactionManager, times(1)).getTransaction(any(TransactionDefinition.class));
    }
}

业务类优化建议

建议使用构造注入替代字段注入,提升代码可测试性和依赖透明度:

@Service // 必须添加@Service注解,让Spring托管该类
public class Service {
    private final DataSourceTransactionManager dataSourceTransactionManager;

    // 构造注入,Spring会自动注入事务管理器实例
    public Service(DataSourceTransactionManager dataSourceTransactionManager) {
        this.dataSourceTransactionManager = dataSourceTransactionManager;
    }

    public int method() {
        TransactionDefinition definition = new DefaultTransactionDefinition();
        TransactionStatus status = dataSourceTransactionManager.getTransaction(definition);
        try {
            doThing();
            dataSourceTransactionManager.commit(status);
            return 1;
        } catch (Exception ex) {
            log.error("exception, bo:{}", bo, ex); // 修正日志参数格式
            dataSourceTransactionManager.rollback(status);
            throw new RuntimeException(ex); // 改用RuntimeException,避免强制声明检查异常
        }
    }

    private void doThing() {
        // 业务逻辑实现
    }
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.28 09:25:47