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

QuarkusTest中如何干净Mock/Spy @Dependent作用域依赖Bean

问题描述
  • 测试类添加@QuarkusTest注解,目标为测试被注入的ServiceA服务
  • ServiceA依赖EventX实例,该实例是标注@Dependent的javax.enterprise.event.Event类型对象
  • 已知@QuarkusMock不支持对该类Bean进行Mock,附带疑问:该限制的产生原因
  • 曾尝试直接修改ServiceA成员变量注入Spy对象的实现,已知该方式存在侵入性,会直接修改Bean实际状态,测试代码如下:
@QuarkusTest
class Test{
  @Inject
  ServiceA serviceA;

  @Inject
  Event<X> eventX;

  @BeforeEach
  void setup(){
    serviceA.eventX = spy(eventX);
    doReturn(true).when(eventX).doSomething();
  }
}
  • 测试运行时,注入的Spy对象会被原始Bean实例覆盖,初步推测为CDI懒加载机制导致
  • 核心诉求:寻找无需过度干预CDI上下文,即可干净Mock/Spy @Dependent作用域Bean的可行方案

回答

@QuarkusMock无法Mock Event实例的原因

@QuarkusMock的生效逻辑是在CDI容器启动的Bean注册阶段,替换匹配类型、限定符的Bean生成逻辑,仅对CDI上下文中统一注册、管理生命周期的普通Bean生效。
javax.enterprise.event.Event不属于普通用户定义Bean,是CDI容器内置的注入点元数据代理:每个注入点获取的Event实例,都是容器根据当前注入点的事件类型、绑定注解动态生成的专属代理,不存在可被全局替换的统一Bean注册条目,因此@QuarkusMock无法对其生效。
注意该限制和@Dependent作用域本身无关,普通的自定义@Dependent Bean默认是支持@QuarkusMock替换的。

之前手动赋值失效的原因

注入的ServiceA是CDI生成的上下文代理对象,直接对代理对象的成员变量赋值,不会写入到代理背后的真实目标实例。首次调用ServiceA的业务方法时,CDI才会初始化真实实例并注入原生Event对象,之前设置的Spy对象自然会被覆盖。

无侵入Mock的可行方案

方案1:针对Event场景的规范原生实现

如果Mock目标是校验事件触发逻辑、拦截事件传递,完全不需要Mock Event实例本身,直接利用CDI的观测者优先级机制即可,全程符合CDI规范,无任何上下文侵入:

import javax.annotation.Priority;
import javax.enterprise.event.Observes;
import javax.interceptor.Interceptor;
import java.util.ArrayList;
import java.util.List;

@QuarkusTest
class Test{
  @Inject
  ServiceA serviceA;

  List<X> firedEvents = new ArrayList<>();

  // 高优先级观测者,最先拿到事件
  void observeEvent(@Observes @Priority(Interceptor.Priority.APPLICATION - 100) X event) {
    firedEvents.add(event);
    // 阻断事件传递给生产代码中的其他观测者
    event.veto();
  }

  @BeforeEach
  void setup(){
    firedEvents.clear();
  }

  @Test
  void testBusinessLogic() {
    serviceA.invokeLogic();
    // 对触发的事件做断言即可
  }
}

方案2:使用Quarkus Mockito扩展的定向注入

Quarkus 1.13及以上版本提供的quarkus-junit5-mockito模块中,@InjectMock注解支持指定Mock实例的注入目标,由Quarkus在字节码增强阶段直接替换对应注入点的实例,不会出现运行时被覆盖的问题:

import io.quarkus.test.junit.mockito.InjectMock;

@QuarkusTest
class Test{
  @Inject
  ServiceA serviceA;

  // 明确指定该Mock实例注入到ServiceA的对应字段中
  @InjectMock(into = ServiceA.class)
  Event<X> eventX;

  @BeforeEach
  void setup(){
    eventX = spy(eventX);
    doReturn(true).when(eventX).doSomething();
  }
}

方案3:轻量CDI测试扩展

如果需要更灵活的控制,可以编写一个仅在测试阶段生效的CDI扩展,监听ProcessInjectionPoint事件,匹配到ServiceA中Event<X>的注入点后,自定义实例提供逻辑返回Spy/Mock对象。该方式在CDI注入阶段就完成实例替换,完全走CDI标准生命周期流程,无侵入性。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.27 04:42:25