如何单元测试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
相关产品推荐
相关产品推荐

