如何正确Mock带有@Startup注解的CDI Bean?
解决Quarkus中Mock @Startup注解CDI Bean的问题
问题原因
标注@Startup的CDI Bean会在应用启动阶段被优先实例化,而普通的@InjectMock(Mockito注解)是在测试类初始化时才注入Mock对象,此时原Bean已经被创建并注入到目标资源类中,导致Mock替换失效,原Bean的构造方法仍会执行。
解决方案
方案1:使用Quarkus官方的@MockBean注解
Quarkus提供的@MockBean注解(来自quarkus-junit5-mockito扩展)会在应用启动前就替换掉CDI容器中的原Bean,确保原Bean不会被实例化,同时让目标资源类注入的是Mock对象。
步骤:
- 确保测试依赖中包含
quarkus-junit5-mockito:
<dependency> <groupId>io.quarkus</groupId> <artifactId>quarkus-junit5-mockito</artifactId> <scope>test</scope> </dependency>
- 修改测试类,将
@InjectMock替换为@MockBean:
@QuarkusTest class ProxyResourceTest { @MockBean private ApiManager apiManagerMock; @Test void testProxyEndpointReturns200AndResponse() { String payload = "expected response"; Mockito.when(apiManagerMock.fetchResponse()).thenReturn(payload); given().contentType(ContentType.JSON) .body(payload) .when().post("/proxy") .then() .statusCode(200) .body(is(payload)); } }
执行测试时,原ApiManager的构造方法不会被调用,ProxyResource注入的是Mock对象,完全由Mockito控制行为。
方案2:拆分@Startup与业务Bean的职责
将原Bean的@Startup注解移除,改为@ApplicationScoped,并把启动初始化逻辑移到@PostConstruct方法中,再通过配置指定该Bean为启动Bean。
修改原ApiManager:
@ApplicationScoped public class ApiManager { private Logger logger = Logger.getLogger(ApiManager.class); @PostConstruct void init() { logger.info("ApiManager warm-up tasks executed"); // 执行预热操作,比如注册定时任务等 } public String fetchResponse() { return "expected response"; } }
添加配置(application.properties):
# 指定该Bean在应用启动时初始化 quarkus.arc.startup-beans=com.yourpackage.ApiManager
此时原Bean的启动初始化逻辑依然保留,同时可以正常使用@InjectMock或@MockBean来Mock,因为它是标准的@ApplicationScoped Bean,Mock替换逻辑能正常生效。
方案3:使用@Alternative + @Priority替换原Bean
在测试类中定义一个带@Alternative和高优先级的Mock Bean,让CDI容器优先选择该Bean实例化,完全绕过原@Startup Bean。
测试类代码:
@QuarkusTest class ProxyResourceTest { @Inject private ApiManager apiManager; // 定义替代Bean,优先级高于原Bean @Alternative @Priority(1) @Startup static class MockApiManager extends ApiManager { // 不调用父类构造方法,避免原初始化逻辑执行 public MockApiManager() {} @Override public String fetchResponse() { return "mock response"; } } @Test void testProxyEndpointReturns200AndResponse() { String payload = "expected response"; // 用Mockito动态控制返回值 ApiManager spyApiManager = Mockito.spy(apiManager); Mockito.when(spyApiManager.fetchResponse()).thenReturn(payload); given().contentType(ContentType.JSON) .body(payload) .when().post("/proxy") .then() .statusCode(200) .body(is(payload)); } }
这种方式下,原ApiManager的构造方法不会被执行,CDI容器会实例化MockApiManager并注入到ProxyResource中。
内容的提问来源于stack exchange,提问作者webcitron
相关产品推荐
相关产品推荐

