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

如何单元测试BindingLifecycleController状态变更:验证消费者暂停逻辑

单元测试方案:生产者绑定故障时消费者暂停逻辑验证

测试依赖

  • spring-boot-starter-test:提供Spring测试基础支持
  • mockito-core:用于模拟依赖对象
  • lombok:支持日志注解简化测试代码

测试实现

测试类基础配置

通过Mockito模拟所有依赖对象,注入到待测试的BindingHealthIndicator中,无需启动完整Spring上下文:

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.boot.actuate.health.Health;
import org.springframework.boot.availability.AvailabilityChangeEvent;
import org.springframework.boot.availability.ReadinessState;
import org.springframework.cloud.stream.binder.Binding;
import org.springframework.cloud.stream.binding.BindingsLifecycleController;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationEventPublisher;
import lombok.extern.slf4j.Slf4j;

import static org.mockito.ArgumentMatchers.*;
import static org.mockito.Mockito.*;

@Slf4j
@ExtendWith(MockitoExtension.class)
public class BindingHealthIndicatorTest {

    private static final String PRODUCER_BINDING_NAME = "consumeAndProcessAllAccountUpdate-out-0";
    private static final String CONSUMER_BINDING_NAME = "consumeAndProcessAllAccountUpdate-in-0";

    @Mock
    private ApplicationContext context;

    @Mock
    private ApplicationEventPublisher eventPublisher;

    @Mock
    private BindingsLifecycleController bindingsController;

    @Mock
    private Binding producerBinding;

    @Mock
    private Binding consumerBinding;

    @InjectMocks
    private BindingHealthIndicator bindingHealthIndicator;
}

测试场景1:生产者绑定查询抛出异常

验证逻辑:查询生产者绑定状态时抛出异常,消费者绑定被设置为PAUSED,发布ReadinessState.REFUSING_TRAFFIC事件,返回down状态的健康检查结果:

@Test
void whenProducerBindingQueryThrowsException_thenConsumerIsPaused() {
    // 模拟上下文返回绑定控制器
    when(context.getBean(BindingsLifecycleController.class)).thenReturn(bindingsController);
    // 模拟查询生产者绑定时抛出异常
    when(bindingsController.queryState(PRODUCER_BINDING_NAME)).thenThrow(new RuntimeException("Producer binding not found"));
    // 模拟消费者绑定查询结果
    when(bindingsController.queryState(CONSUMER_BINDING_NAME)).thenReturn(consumerBinding);
    when(consumerBinding.isRunning()).thenReturn(true);

    // 执行健康检查
    Health health = bindingHealthIndicator.health();

    // 验证消费者绑定被暂停
    verify(bindingsController, times(1))
            .changeState(CONSUMER_BINDING_NAME, BindingsLifecycleController.State.PAUSED);
    // 验证发布服务不可用事件
    verify(eventPublisher, times(1))
            .publishEvent(any(AvailabilityChangeEvent.class));
    // 验证健康状态返回down
    assert health.getStatus().equals(Health.down().getStatus());
    assert health.getDetails().get("description").equals("The Src topic is unavailable");
    // 验证错误日志输出
    verify(log, times(1)).error("The Src topic is unavailable");
}

测试场景2:生产者绑定isRunning返回false

验证逻辑:生产者绑定存在但状态非运行,消费者绑定被设置为PAUSED,发布ReadinessState.REFUSING_TRAFFIC事件,返回down状态的健康检查结果:

@Test
void whenProducerBindingNotRunning_thenConsumerIsPaused() {
    // 模拟上下文返回绑定控制器
    when(context.getBean(BindingsLifecycleController.class)).thenReturn(bindingsController);
    // 模拟生产者绑定状态为非运行
    when(bindingsController.queryState(PRODUCER_BINDING_NAME)).thenReturn(producerBinding);
    when(producerBinding.isRunning()).thenReturn(false);
    // 模拟消费者绑定查询结果
    when(bindingsController.queryState(CONSUMER_BINDING_NAME)).thenReturn(consumerBinding);
    when(consumerBinding.isRunning()).thenReturn(true);

    // 执行健康检查
    Health health = bindingHealthIndicator.health();

    // 验证消费者绑定被暂停
    verify(bindingsController, times(1))
            .changeState(CONSUMER_BINDING_NAME, BindingsLifecycleController.State.PAUSED);
    // 验证发布服务不可用事件
    verify(eventPublisher, times(1))
            .publishEvent(any(AvailabilityChangeEvent.class));
    // 验证健康状态返回down
    assert health.getStatus().equals(Health.down().getStatus());
    assert health.getDetails().get("description").equals("The Src topic is unavailable");
    // 验证错误日志输出
    verify(log, times(1)).error("The Src topic is unavailable");
}

补充测试:正常状态下消费者恢复运行

验证逻辑:当生产者和消费者绑定均正常时,消费者绑定被设置为RESUMED,发布ReadinessState.ACCEPTING_TRAFFIC事件,返回up状态的健康检查结果:

@Test
void whenAllBindingsHealthy_thenConsumerIsResumed() {
    when(context.getBean(BindingsLifecycleController.class)).thenReturn(bindingsController);
    when(bindingsController.queryState(PRODUCER_BINDING_NAME)).thenReturn(producerBinding);
    when(producerBinding.isRunning()).thenReturn(true);
    when(bindingsController.queryState(CONSUMER_BINDING_NAME)).thenReturn(consumerBinding);
    when(consumerBinding.isRunning()).thenReturn(true);

    Health health = bindingHealthIndicator.health();

    verify(bindingsController, times(1))
            .changeState(CONSUMER_BINDING_NAME, BindingsLifecycleController.State.RESUMED);
    verify(eventPublisher, times(1))
            .publishEvent(argThat(event -> 
                    ((AvailabilityChangeEvent<?>) event).getState() == ReadinessState.ACCEPTING_TRAFFIC));
    assert health.getStatus().equals(Health.up().getStatus());
    assert health.getDetails().get("description").equals("The Consumer and producer topics are Healthy");
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.30 19:39:24