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

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>

问题原因与解决方案

核心原因

  1. 作用域不匹配:RedisService是@ApplicationScoped,依赖它的DataService是@RequestScoped,Quarkus的Mock注入可能因Bean生命周期冲突,导致真实实例被优先注入。
  2. 构造函数注入的Mock支持:RedisService通过构造函数注入RedisDataSource,默认情况下@InjectMock需要确保能覆盖构造函数注入的Bean。
  3. 集成测试特性:@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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.23 16:29:51