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

