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() { // 业务逻辑 } }
问题根源
- 初始化冲突:同时使用
SpringRunner(管理Spring上下文)和MockitoAnnotations.initMocks(this)(Mockito独立初始化),导致Spring托管的@SpyBean实例被Mockito重置为null,触发空指针。 - 提前调用真实方法:
@Before中直接调用dataSourceTransactionManager.getTransaction(def),此时SpyBean还未完成Mock配置,会执行真实逻辑,而Spring上下文可能未完全加载,导致真实方法内部抛出空指针。 - 参数匹配失效:业务类中创建的
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
相关产品推荐
相关产品推荐

