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:
2026-06-29 12:01:49 +08:00
parent 0b950b3c35
commit 79374ea6ec

View File

@@ -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:6379docker redis-stream-it * 真实 Redis 要求localhost:6379docker 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"); // 纯装配检查,不依赖真实 RedisLettuce 懒连接)。
redisStreamRunner().run(ctx -> { new ApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(
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).hasSingleBean(MqDriver.class);
assertThat(ctx.getBean(MqDriver.class)).isInstanceOf(RedisStreamMqDriver.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;不装 RedisAutoConfigurationredis 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);
}); });
} }