Quarkus测试中@InjectMock注入RedisService失效,接口返回空列表
Quarkus REST应用Mock RedisService不生效问题排查与解决
问题描述
我有一个操作Redis的简易Quarkus REST应用,编写接口测试时遵循Quarkus Mocking规范,但Mock无法返回预设数据,实际返回空列表,推测是注入了真实的RedisService实例而非Mock实例。
依赖调用链:DataEndpointResource -> DataService -> RedisService,目标是Mock RedisService。
组件代码
DataEndpointResource
@Path("/data") @RequestScoped public class DataEndpointResource { @Inject DataService service; @GET @Produces(MediaType.APPLICATION_JSON) public Response getAllValues() { Collection<DataRecord> values; try { values = service.getAllValues(); } catch (RedisException ex) { return Response.serverError().build(); } return Response.ok(values).build(); } }
DataService
@RequestScoped public class DataService { @Inject private RedisService redisService; public Collection<DataRecord> getAllValues() throws RedisException { return this.redisService.getAllValues(); } }
RedisService
@ApplicationScoped public class RedisService { ReactiveRedisDataSource ds; KeyCommands<String> keyCommands; ValueCommands<String, ObjectLockRecord> valueCommands; public RedisService(RedisDataSource ds) { keyCommands = ds.key(); valueCommands = ds.value(ObjectLockRecord.class); } public Collection<DataRecord> getAllValues() throws RedisException { try { // 执行Redis操作并返回数据 } catch (Exception ex) { log("Error obtaining values", ex); throw new RedisException(ex); } } }
测试代码
集成测试类
@QuarkusIntegrationTest public class DataEndpointResourceIT extends DataEndpointResourceTest { }
基础测试类
import io.quarkus.test.InjectMock; import io.quarkus.test.junit.QuarkusTest; import static io.restassured.RestAssured.given; import io.restassured.common.mapper.TypeRef; import io.restassured.http.ContentType; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.MethodOrderer; import org.junit.jupiter.api.Order; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; import org.junit.jupiter.api.TestMethodOrder; import static org.mockito.Mockito.when; @QuarkusTest @TestInstance(TestInstance.Lifecycle.PER_CLASS) @TestMethodOrder(MethodOrderer.OrderAnnotation.class) @DisplayName("Data REST Resource") public class DataEndpointResourceTest { @InjectMock RedisService redisServiceMock; Map<String, DataRecord> testData; @BeforeEach public void setUp() throws RedisException { testData = new HashMap<>(); DataRecord r = new DataRecord("1234", "data1"); testData.put(r.getKey(), r); r = new DataRecord("asdf", "data2"); testData.put(r.getKey(), r); } @Test @Order(1) @DisplayName("Endpoint: GET /data") public void getData() throws RedisException { when(redisServiceMock.getAllValues()).thenReturn(new ArrayList<>(testData.values())); List<DataRecord> list = given() .accept(ContentType.JSON) .when() .get("/api/v1/data") .then() .statusCode(200) .extract().body().as(new TypeRef<>() { }); Assertions.assertNotNull(list); Assertions.assertEquals(2, list.size()); } }
测试失败结果
------------------------------------------------------- T E S T S ------------------------------------------------------- | +-- Data REST Resource | +-- [XX] Endpoint: GET /data - 0.728 ss Results: Failures: DataEndpointResourceTest.getData:79 expected: <2> but was: <0>
问题原因与解决方案
核心原因
- 作用域不匹配:
RedisService是@ApplicationScoped,依赖它的DataService是@RequestScoped,Quarkus的Mock注入可能因Bean生命周期冲突,导致真实实例被优先注入。 - 构造函数注入的Mock支持:
RedisService通过构造函数注入RedisDataSource,默认情况下@InjectMock需要确保能覆盖构造函数注入的Bean。 - 集成测试特性:
@QuarkusIntegrationTest会启动完整应用,Mock生效逻辑与@QuarkusTest不同,需额外配置。
解决方案
方案1:统一Bean作用域
将RedisService的作用域改为@RequestScoped,与DataService保持一致,避免生命周期冲突:
@RequestScoped public class RedisService { ReactiveRedisDataSource ds; KeyCommands<String> keyCommands; ValueCommands<String, ObjectLockRecord> valueCommands; @Inject public RedisService(RedisDataSource ds) { keyCommands = ds.key(); valueCommands = ds.value(ObjectLockRecord.class); } // 其余代码不变 }
方案2:手动注入Mock实例
若@InjectMock不生效,可手动创建Mock并替换DataService中的依赖:
@QuarkusTest @TestInstance(TestInstance.Lifecycle.PER_CLASS) @TestMethodOrder(MethodOrderer.OrderAnnotation.class) @DisplayName("Data REST Resource") public class DataEndpointResourceTest { @Inject DataService dataService; RedisService redisServiceMock; @BeforeEach public void setUp() throws Exception { redisServiceMock = Mockito.mock(RedisService.class); // 通过反射替换DataService中的redisService字段 Field field = DataService.class.getDeclaredField("redisService"); field.setAccessible(true); field.set(dataService, redisServiceMock); testData = new HashMap<>(); DataRecord r = new DataRecord("1234", "data1"); testData.put(r.getKey(), r); r = new DataRecord("asdf", "data2"); testData.put(r.getKey(), r); } @Test @Order(1) @DisplayName("Endpoint: GET /data") public void getData() throws RedisException { when(redisServiceMock.getAllValues()).thenReturn(new ArrayList<>(testData.values())); List<DataRecord> list = given() .accept(ContentType.JSON) .when() .get("/api/v1/data") .then() .statusCode(200) .extract().body().as(new TypeRef<>() {}); Assertions.assertNotNull(list); Assertions.assertEquals(2, list.size()); } }
方案3:配置集成测试Mock
若运行@QuarkusIntegrationTest,需在src/test/resources/application.properties中禁用真实Redis连接:
quarkus.redis.enabled=false quarkus.redis.devservices.enabled=false
同时确保Quarkus版本在2.10+,该版本对集成测试的Mock支持更完善。
方案4:验证Mock注入状态
在测试中添加断言,验证DataService中注入的是否为Mock实例:
@Test public void getData() throws Exception { Field field = DataService.class.getDeclaredField("redisService"); field.setAccessible(true); RedisService injectedService = (RedisService) field.get(dataService); Assertions.assertTrue(Mockito.mockingDetails(injectedService).isMock()); when(redisServiceMock.getAllValues()).thenReturn(new ArrayList<>(testData.values())); // 其余测试代码不变 }
内容的提问来源于stack exchange,提问作者John Manko
相关产品推荐
相关产品推荐

