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

