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

将JMockit Mockup改写为Mockito:如何拦截依赖API的方法调用?

Mockito能否拦截非显式依赖对象的方法?

你尝试将JMockit的全局拦截逻辑改写为Mockito实现,但遇到了自定义setId方法从未执行的问题。原JMockit代码通过MockUp<EntityManagerImpl>可以拦截所有EntityManagerImpl实例的persist和merge方法,无需显式注入Mock;但Mockito代码中即使创建了EntityManagerImpl的Mock,也无法拦截被测类内部通过EntityManagerFactory.createEntityManager()创建的实例。

原JMockit代码

protected void initializeEntityManagerInterception() throws Exception {       
    new MockUp<EntityManagerImpl>() { 
        @Mock
        public void persist(Invocation i, Object o) {
            setId(o); // 自定义方法
            i.proceed(o);
        }
 
        @Mock
        public Object merge(Invocation i, Object o) {
            setId(o);
            return i.proceed(o);
        }
    };
}

尝试的Mockito代码(无效)

protected void initializeEntityManagerInterception() throws Exception {
    EntityManagerImpl entityManagerImpl = mock(EntityManagerImpl.class);
 
    Answer<Void> persist = new Answer<Void>() {
        public Void answer(final InvocationOnMock invocation) throws Throwable {
            Object obj = invocation.getArguments()[0];
            setId(obj);
            invocation.callRealMethod();
            return null;
        }
    };      
    doAnswer(persist).when(entityManagerImpl).persist(any(Object.class));       
 
    Answer<Object> merge = new Answer<Object>() {
        public Object answer(final InvocationOnMock invocation) throws Throwable {
            Object obj = invocation.getArguments()[0];
            setId(obj);
            return invocation.callRealMethod();
        }
    };     
    doAnswer(merge).when(entityManagerImpl).merge(any(Object.class));
}

补充场景

  • 测试类继承自BaseTest,拦截逻辑定义在父类
  • 被测类ClassUnderTest运行在本地Jetty部署的Apache Camel路由中,内部通过exchange.getContext().getRegistry()获取EntityManagerFactory,再创建EntityManager实例
  • 测试类中尝试过@InjectMocks和手动创建Mock,但均未生效

核心原因:JMockit与Mockito的实现差异

  • JMockit:基于全局字节码修改,会修改目标类的字节码,所有该类的实例(不管是哪里创建的)都会应用Mock逻辑,因此无需显式注入就能拦截。
  • Mockito:基于动态代理,仅对显式创建的Mock/Spy实例生效,无法拦截被测类内部通过工厂、new等方式创建的对象实例——你创建的EntityManagerImplMock根本没有被被测类使用。

解决方案

方案1:重构被测类(推荐,符合依赖注入原则)

修改被测类,将EntityManagerFactory或EntityManager作为依赖注入,而不是在内部通过注册表获取。这样就能直接Mock注入的依赖:

修改后的被测类

public class ClassUnderTest implements Processor {
    // 注入EntityManagerFactory
    private final EntityManagerFactory entityManagerFactory;

    public ClassUnderTest(EntityManagerFactory entityManagerFactory) {
        this.entityManagerFactory = entityManagerFactory;
    }

    @Override
    public void process(Exchange exchange) throws Exception {
        EntityManager entityManager = null; 
        try {
            entityManager = entityManagerFactory.createEntityManager();
            // 原有逻辑
            entityManager.persist(someObject);
            exchange.getMessage().setBody(someOtherObject);
        } catch(Exception ex) {
            // 异常处理
        } finally {
            closeEntityManager(entityManager);
        }
    }
}

测试类调整

public class MyTest extends BaseTest {
    @InjectMocks
    private ClassUnderTest classUnderTest; 

    @Mock
    private EntityManagerFactory entityManagerFactory;
    @Mock
    private EntityManagerImpl entityManagerImpl;
    @Mock(answer = Answers.RETURNS_DEEP_STUBS)    
    private Exchange exchange;

    @BeforeEach
    public void before() throws Exception {
        super.before();       
        MockitoAnnotations.openMocks(this);
        // 让工厂返回Mock的EntityManager
        when(entityManagerFactory.createEntityManager()).thenReturn(entityManagerImpl);
        
        // 定义拦截逻辑
        doAnswer(invocation -> {
            setId(invocation.getArguments()[0]);
            invocation.callRealMethod();
            return null;
        }).when(entityManagerImpl).persist(any(Object.class));

        doAnswer(invocation -> {
            Object obj = invocation.getArguments()[0];
            setId(obj);
            return invocation.callRealMethod();
        }).when(entityManagerImpl).merge(any(Object.class));
    }

    // 测试方法不变
}

方案2:使用PowerMock扩展Mockito(无需重构)

如果无法修改被测类,可以用PowerMock实现类级别的全局Mock,拦截EntityManagerFactory.createEntityManager()方法,让它返回你创建的Mock实例:

依赖配置(以Maven为例)

<dependency>
    <groupId>org.powermock</groupId>
    <artifactId>powermock-module-junit4</artifactId>
    <version>2.0.9</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.powermock</groupId>
    <artifactId>powermock-api-mockito2</artifactId>
    <version>2.0.9</version>
    <scope>test</scope>
</dependency>

测试类调整

@RunWith(PowerMockRunner.class)
@PrepareForTest({EntityManagerFactory.class}) // 指定需要修改字节码的类
public class MyTest extends BaseTest {
    @InjectMocks
    private ClassUnderTest classUnderTest; 

    @Mock
    private EntityManagerImpl entityManagerImpl;
    @Mock(answer = Answers.RETURNS_DEEP_STUBS)    
    private Exchange exchange;
    @Mock
    private EntityManagerFactory entityManagerFactory;

    @BeforeEach
    public void before() throws Exception {
        super.before();       
        MockitoAnnotations.openMocks(this);
        
        // 拦截注册表获取EntityManagerFactory的逻辑
        when(exchange.getContext().getRegistry()
             .lookupByNameAndType("entityManagerFactory", EntityManagerFactory.class))
             .thenReturn(entityManagerFactory);
        
        // 让工厂返回Mock的EntityManager
        when(entityManagerFactory.createEntityManager()).thenReturn(entityManagerImpl);
        
        // 定义persist/merge的拦截逻辑
        doAnswer(invocation -> {
            setId(invocation.getArguments()[0]);
            invocation.callRealMethod();
            return null;
        }).when(entityManagerImpl).persist(any(Object.class));

        doAnswer(invocation -> {
            Object obj = invocation.getArguments()[0];
            setId(obj);
            return invocation.callRealMethod();
        }).when(entityManagerImpl).merge(any(Object.class));
    }

    // 测试方法不变
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.19 11:44:52