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

Quarkus Kafka消费者测试中@InjectMock失效:Mock对象被移除导致调用实际服务的解决方法

解决Quarkus Kafka消费者测试中@InjectMock失效的问题

我之前在Quarkus的Kafka消费者测试里也碰到过一模一样的问题——明明用了@InjectMock,结果实际的TestServiceImpl还是被调用了。核心原因是Kafka消费者的初始化时机早于Mock对象的注入:Quarkus启动时会先初始化带@Incoming注解的Kafka消费者(它是CDI管理的bean),此时TestService的实际实例已经被绑定到消费者中,后续注入的Mock对象无法替换这个已绑定的实例。

下面是几种靠谱的解决方法,按推荐程度排序:

1. 改用构造注入(最简洁)

把消费者类中的字段注入改成构造注入,这样CDI容器在创建消费者实例时,会直接使用测试上下文里的Mock版TestService,而不是提前绑定实际实现。

修改你的Kafka消费者类:

@ApplicationScoped // 确保消费者是CDI管理的bean
public class Consumer {
    private final TestService service;

    // 构造注入TestService
    @Inject
    public Consumer(TestService service) {
        this.service = service;
    }

    @Incoming("request-in")
    @Blocking
    @Acknowledgment(Acknowledgment.Strategy.POST_PROCESSING)
    public void consume(Event e) {
        service.test(...); // 这里会自动使用Mock实例
    }
}

你的测试代码不需要大改,保持原来的@InjectMock即可:

@QuarkusTest
public class ConsumerTest {
    @InjectMock TestService service;
    @Inject @Any InMemoryConnector connector;

    @Test
    public void testEvent() {
        Mockito.when(service.test(Mockito.any(Product.class)))
               .thenReturn("1234");
        InMemorySource source = connector.source("request-in");
        source.send(e);
        
        // 别忘了验证Mock方法是否被调用
        Mockito.verify(service).test(Mockito.any(Product.class));
    }
}

2. 手动替换消费者中的服务实例

如果不想修改消费者的注入方式,可以在测试中通过反射或setter方法,把Mock对象手动注入到已初始化的消费者实例中:

首先确保消费者类是CDI bean(标注@ApplicationScoped),然后测试类中注入消费者实例,在@BeforeEach里替换服务:

@QuarkusTest
public class ConsumerTest {
    @InjectMock TestService service;
    @Inject @Any InMemoryConnector connector;
    @Inject Consumer consumer; // 注入消费者实例

    @BeforeEach
    void setUp() throws NoSuchFieldException, IllegalAccessException {
        // 通过反射替换消费者中的service字段
        Field serviceField = Consumer.class.getDeclaredField("service");
        serviceField.setAccessible(true);
        serviceField.set(consumer, service);
    }

    @Test
    public void testEvent() {
        Mockito.when(service.test(Mockito.any(Product.class)))
               .thenReturn("1234");
        InMemorySource source = connector.source("request-in");
        source.send(e);
        Mockito.verify(service).test(Mockito.any(Product.class));
    }
}

如果消费者类提供了setService方法,就不用反射了,直接调用consumer.setService(service)更优雅。

3. 使用@Alternative替换实际实现

创建一个测试专用的Mock服务类,通过@Alternative和@Priority让它在测试时优先于实际实现被CDI容器选中:

  1. 创建Mock服务类:
@Alternative
@Priority(1) // 优先级高于实际实现(默认优先级是0)
@ApplicationScoped
public class MockTestService implements TestService {
    private final TestService delegate = Mockito.mock(TestService.class);

    @Override
    public String test(Product product) {
        return delegate.test(product);
    }

    // 暴露delegate给测试类设置Mock行为
    public TestService getDelegate() {
        return delegate;
    }
}
  1. 修改测试类,注入这个Mock服务并设置行为:
@QuarkusTest
public class ConsumerTest {
    @Inject MockTestService mockService;
    @Inject @Any InMemoryConnector connector;

    @Test
    public void testEvent() {
        Mockito.when(mockService.getDelegate().test(Mockito.any(Product.class)))
               .thenReturn("1234");
        InMemorySource source = connector.source("request-in");
        source.send(e);
        Mockito.verify(mockService.getDelegate()).test(Mockito.any(Product.class));
    }
}

前置检查

最后别忘了确认几个基础配置:

  • TestServiceImpl类必须标注@ApplicationScoped(或其他CDI作用域注解),确保它是CDI管理的bean。
  • 测试依赖中已经包含quarkus-junit5和quarkus-mockito扩展:
<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-junit5</artifactId>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-mockito</artifactId>
    <scope>test</scope>
</dependency>

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.28 20:27:27