test(message): 切片 wiring 测试改用真实 autoconfig 顺序
MqSliceWiringTest 从 withUserConfiguration 手动排序改为 AutoConfigurations.of (等价真实 boot 的 AutoConfigurationSorter),并断言 MqTemplate / MqListenerRegistry 在真实顺序下装配,防止 core 装配顺序回归;配合 mq-starter driver autoconfig 的 @AutoConfigureBefore 修复。同时去掉 driver 选择用例多余的 Redis 守卫。 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -5,6 +5,8 @@ import com.njcn.message.message.RecallMessage;
|
|||||||
import com.njcn.message.messagedto.MessageDataDTO;
|
import com.njcn.message.messagedto.MessageDataDTO;
|
||||||
import com.njcn.middle.stream.autoconfig.RedisStreamAutoConfiguration;
|
import com.njcn.middle.stream.autoconfig.RedisStreamAutoConfiguration;
|
||||||
import com.njcn.mq.autoconfig.MqCoreAutoConfiguration;
|
import com.njcn.mq.autoconfig.MqCoreAutoConfiguration;
|
||||||
|
import com.njcn.mq.container.MqListenerRegistry;
|
||||||
|
import com.njcn.mq.core.MqTemplate;
|
||||||
import com.njcn.mq.driver.redis.RedisStreamMqDriver;
|
import com.njcn.mq.driver.redis.RedisStreamMqDriver;
|
||||||
import com.njcn.mq.driver.redis.RedisStreamMqDriverAutoConfiguration;
|
import com.njcn.mq.driver.redis.RedisStreamMqDriverAutoConfiguration;
|
||||||
import com.njcn.mq.driver.rocketmq.RocketMqDriver;
|
import com.njcn.mq.driver.rocketmq.RocketMqDriver;
|
||||||
@@ -15,6 +17,7 @@ import org.apache.rocketmq.spring.autoconfigure.RocketMQProperties;
|
|||||||
import org.apache.rocketmq.spring.core.RocketMQTemplate;
|
import org.apache.rocketmq.spring.core.RocketMQTemplate;
|
||||||
import org.junit.jupiter.api.Assumptions;
|
import org.junit.jupiter.api.Assumptions;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.springframework.boot.autoconfigure.AutoConfigurations;
|
||||||
import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration;
|
import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration;
|
||||||
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
|
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
|
||||||
import org.springframework.data.redis.connection.stream.MapRecord;
|
import org.springframework.data.redis.connection.stream.MapRecord;
|
||||||
@@ -38,12 +41,16 @@ import static org.mockito.Mockito.verify;
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* MQ starter 切片接入验证测试(ApplicationContextRunner,不启动全应用)。
|
* MQ starter 切片接入验证测试(ApplicationContextRunner,不启动全应用)。
|
||||||
* 真实 Redis 要求:localhost:6379(docker redis-stream-it)。
|
* 真实 Redis 要求:localhost:6379(docker redis-stream-it);仅上/下行收发用例需要,
|
||||||
* 无 Redis 时,依赖 Redis 的用例通过 Assumptions 跳过。
|
* 纯装配用例不需要(Lettuce 懒连接,不触发实际连接)。
|
||||||
*
|
*
|
||||||
* <p>注意:withConfiguration(AutoConfigurations.of(...)) 会对 autoconfig 类名做字母序排序,
|
* <p>装配一律用 {@code withConfiguration(AutoConfigurations.of(...))},让 Spring 按
|
||||||
* 导致 MqCoreAutoConfiguration 在 driver autoconfig 之前处理,@ConditionalOnBean(MqDriver.class)
|
* <b>真实 autoconfig 顺序</b>(AutoConfigurationSorter)处理,与生产环境一致。以此
|
||||||
* 因此失败。改用 withUserConfiguration(...) 按依赖顺序显式列出,避免排序问题。
|
* 验证 {@link MqCoreAutoConfiguration} 的 {@code @ConditionalOnBean(MqDriver)} 能在
|
||||||
|
* 真实顺序下拿到 driver bean —— 这依赖 driver autoconfig 上的
|
||||||
|
* {@code @AutoConfigureBefore(MqCoreAutoConfiguration.class)}。若缺该保障,按类名字母序
|
||||||
|
* {@code com.njcn.mq.autoconfig.*} 先于 {@code com.njcn.mq.driver.*},core 会在 driver
|
||||||
|
* bean 注册前求值条件失败,导致 MqTemplate / MqListenerRegistry 不装配。
|
||||||
*/
|
*/
|
||||||
public class MqSliceWiringTest {
|
public class MqSliceWiringTest {
|
||||||
|
|
||||||
@@ -58,64 +65,67 @@ public class MqSliceWiringTest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/** redis-stream 模式:真实 autoconfig 顺序装 5 个 autoconfig + 业务 bean(上/下行用)。 */
|
||||||
* redis-stream 模式的基础 runner。
|
|
||||||
* 按依赖顺序列出:Redis → Phase1 stream → redis driver → rocket driver(inactive) → mq core → 业务 bean
|
|
||||||
* 这样 @ConditionalOnBean(MqDriver.class) 在 MqCoreAutoConfiguration 被解析时已能找到 MqDriver 定义。
|
|
||||||
*/
|
|
||||||
private ApplicationContextRunner redisStreamRunner() {
|
private ApplicationContextRunner redisStreamRunner() {
|
||||||
return new ApplicationContextRunner()
|
return new ApplicationContextRunner()
|
||||||
.withUserConfiguration(
|
.withConfiguration(AutoConfigurations.of(
|
||||||
RedisAutoConfiguration.class, // 1. StringRedisTemplate
|
RedisAutoConfiguration.class,
|
||||||
RedisStreamAutoConfiguration.class, // 2. StreamKeyBuilder, RedisStreamProperties
|
RedisStreamAutoConfiguration.class,
|
||||||
RedisStreamMqDriverAutoConfiguration.class, // 3. MqDriver(RedisStreamMqDriver)
|
RedisStreamMqDriverAutoConfiguration.class,
|
||||||
RocketMqDriverAutoConfiguration.class, // 4. inactive for redis-stream
|
RocketMqDriverAutoConfiguration.class,
|
||||||
MqCoreAutoConfiguration.class, // 5. MqTemplate, MqListenerRegistry
|
MqCoreAutoConfiguration.class))
|
||||||
FrontDataMqListener.class, // 6. 上行 listener
|
.withUserConfiguration(FrontDataMqListener.class, RecallMqSender.class)
|
||||||
RecallMqSender.class // 7. 下行 sender
|
|
||||||
)
|
|
||||||
.withBean(MessAnalysisFeignClient.class,
|
.withBean(MessAnalysisFeignClient.class,
|
||||||
() -> mock(MessAnalysisFeignClient.class))
|
() -> mock(MessAnalysisFeignClient.class))
|
||||||
.withPropertyValues(
|
.withPropertyValues(
|
||||||
"mq.type=redis-stream",
|
"mq.type=redis-stream",
|
||||||
"spring.redis.host=localhost",
|
"spring.redis.host=localhost",
|
||||||
"spring.redis.port=6379"
|
"spring.redis.port=6379");
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ------------------------------------------------------------------
|
// ------------------------------------------------------------------
|
||||||
// 1. Driver 二选一
|
// 1. 装配:mq.type 二选一 + core bean 在真实 autoconfig 顺序下装配
|
||||||
// ------------------------------------------------------------------
|
// ------------------------------------------------------------------
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testRedisStreamDriverSelected() {
|
void testRedisStreamDriverSelected() {
|
||||||
Assumptions.assumeTrue(redisAvailable(), "Redis not available at localhost:6379, skipping");
|
// 纯装配检查,不依赖真实 Redis(Lettuce 懒连接)。
|
||||||
redisStreamRunner().run(ctx -> {
|
new ApplicationContextRunner()
|
||||||
assertThat(ctx).hasSingleBean(MqDriver.class);
|
.withConfiguration(AutoConfigurations.of(
|
||||||
assertThat(ctx.getBean(MqDriver.class)).isInstanceOf(RedisStreamMqDriver.class);
|
RedisAutoConfiguration.class,
|
||||||
});
|
RedisStreamAutoConfiguration.class,
|
||||||
|
RedisStreamMqDriverAutoConfiguration.class,
|
||||||
|
RocketMqDriverAutoConfiguration.class,
|
||||||
|
MqCoreAutoConfiguration.class))
|
||||||
|
.withPropertyValues(
|
||||||
|
"mq.type=redis-stream",
|
||||||
|
"spring.redis.host=localhost",
|
||||||
|
"spring.redis.port=6379")
|
||||||
|
.run(ctx -> {
|
||||||
|
assertThat(ctx).hasSingleBean(MqDriver.class);
|
||||||
|
assertThat(ctx.getBean(MqDriver.class)).isInstanceOf(RedisStreamMqDriver.class);
|
||||||
|
// 真实顺序下 core 的 @ConditionalOnBean(MqDriver) 必须命中
|
||||||
|
assertThat(ctx).hasSingleBean(MqTemplate.class);
|
||||||
|
assertThat(ctx).hasSingleBean(MqListenerRegistry.class);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testRocketMqDriverSelected() {
|
void testRocketMqDriverSelected() {
|
||||||
// rocket 测试不依赖真实 Redis,仅引入 rocket 相关 + core
|
// rocket 测试不依赖真实 Redis;不装 RedisAutoConfiguration,redis driver 因 mq.type 不匹配而 inactive。
|
||||||
new ApplicationContextRunner()
|
new ApplicationContextRunner()
|
||||||
.withUserConfiguration(
|
.withConfiguration(AutoConfigurations.of(
|
||||||
RocketMqDriverAutoConfiguration.class, // 1. MqDriver(RocketMqDriver)
|
RedisStreamMqDriverAutoConfiguration.class,
|
||||||
RedisStreamMqDriverAutoConfiguration.class, // 2. inactive for rocketmq
|
RocketMqDriverAutoConfiguration.class,
|
||||||
MqCoreAutoConfiguration.class, // 3. MqTemplate, MqListenerRegistry
|
MqCoreAutoConfiguration.class))
|
||||||
FrontDataMqListener.class, // 4. inactive for rocketmq
|
.withBean(RocketMQTemplate.class, () -> mock(RocketMQTemplate.class))
|
||||||
RecallMqSender.class // 5. sender
|
|
||||||
)
|
|
||||||
.withBean(MessAnalysisFeignClient.class,
|
|
||||||
() -> mock(MessAnalysisFeignClient.class))
|
|
||||||
.withBean(RocketMQTemplate.class,
|
|
||||||
() -> mock(RocketMQTemplate.class))
|
|
||||||
.withBean(RocketMQProperties.class, RocketMQProperties::new)
|
.withBean(RocketMQProperties.class, RocketMQProperties::new)
|
||||||
.withPropertyValues("mq.type=rocketmq")
|
.withPropertyValues("mq.type=rocketmq")
|
||||||
.run(ctx -> {
|
.run(ctx -> {
|
||||||
assertThat(ctx).hasSingleBean(MqDriver.class);
|
assertThat(ctx).hasSingleBean(MqDriver.class);
|
||||||
assertThat(ctx.getBean(MqDriver.class)).isInstanceOf(RocketMqDriver.class);
|
assertThat(ctx.getBean(MqDriver.class)).isInstanceOf(RocketMqDriver.class);
|
||||||
|
assertThat(ctx).hasSingleBean(MqTemplate.class);
|
||||||
|
assertThat(ctx).hasSingleBean(MqListenerRegistry.class);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user