using Dpz.Core.Entity.Base;
using Dpz.Core.MessageQueue.Abstractions;
using Dpz.Core.MessageQueue.Configuration;
using Dpz.Core.MessageQueue.Extensions;
using Dpz.Core.MessageQueue.Models;
using Dpz.Core.MessageQueue.RabbitMQ;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Options;
namespace Dpz.Core.MessageQueue.Test.Extensions;
public class MessageQueueServiceExtensionsTests
{
[Fact]
public async Task AddRabbitMQ_ShouldRegisterCoreServicesAndOptions()
{
var services = new ServiceCollection();
services.AddLogging();
var configuration = new ConfigurationBuilder()
.AddInMemoryCollection(
new Dictionary<string, string?>
{
["RabbitMQ:HostName"] = "mq.test.local",
["RabbitMQ:Port"] = "5673",
["RabbitMQ:UserName"] = "u",
["RabbitMQ:Password"] = "p",
["RabbitMQ:VirtualHost"] = "/v",
}
)
.Build();
services.AddRabbitMQ(configuration);
await using var provider = services.BuildServiceProvider();
var options = provider.GetRequiredService<IOptions<RabbitMQOptions>>().Value;
Assert.Equal("mq.test.local", options.HostName);
Assert.Equal(5673, options.Port);
var routing = provider.GetRequiredService<IMessageRoutingConvention>();
Assert.IsType<DefaultMessageRoutingConvention>(routing);
var factory = provider.GetRequiredService<IRabbitMQConnectionFactory>();
Assert.IsType<RabbitMQConnectionFactory>(factory);
var publisher = provider.GetRequiredService<IMessagePublisher<TestMessage>>();
Assert.IsType<RabbitMQPublisher<TestMessage>>(publisher);
}
[Fact]
public void AddMessageConsumer_ShouldRegisterHandlerAndHostedService()
{
var services = new ServiceCollection();
services.AddMessageConsumer<TestMessage, TestMessageHandler>();
var handlerDescriptor = services.SingleOrDefault(x =>
x.ServiceType == typeof(IMessageHandler<TestMessage>)
);
Assert.NotNull(handlerDescriptor);
Assert.Equal(ServiceLifetime.Scoped, handlerDescriptor.Lifetime);
Assert.Equal(typeof(TestMessageHandler), handlerDescriptor.ImplementationType);
var hostedServiceDescriptor = services.LastOrDefault(x =>
x.ServiceType == typeof(IHostedService)
);
Assert.NotNull(hostedServiceDescriptor);
Assert.Equal(
typeof(RabbitMQConsumerBackgroundService<TestMessage>),
hostedServiceDescriptor.ImplementationType
);
}
[Fact]
public void AddMessageConsumerWithResult_ShouldRegisterHandlerAndHostedService()
{
var services = new ServiceCollection();
services.AddMessageConsumer<
TestMessage,
TestMessageResultHandler,
MessageHandlerResult<string>
>();
var handlerDescriptor = services.SingleOrDefault(x =>
x.ServiceType == typeof(IMessageHandler<TestMessage, MessageHandlerResult<string>>)
);
Assert.NotNull(handlerDescriptor);
Assert.Equal(ServiceLifetime.Scoped, handlerDescriptor.Lifetime);
Assert.Equal(typeof(TestMessageResultHandler), handlerDescriptor.ImplementationType);
var hostedServiceDescriptor = services.LastOrDefault(x =>
x.ServiceType == typeof(IHostedService)
);
Assert.NotNull(hostedServiceDescriptor);
Assert.Equal(
typeof(RabbitMQConsumerBackgroundServiceWithResult<
TestMessage,
MessageHandlerResult<string>
>),
hostedServiceDescriptor.ImplementationType
);
}
[Fact]
public void AddCustomRoutingConvention_ShouldReplaceRoutingConvention()
{
var services = new ServiceCollection();
services.AddCustomRoutingConvention<CustomRoutingConvention>();
using var provider = services.BuildServiceProvider();
var routing = provider.GetRequiredService<IMessageRoutingConvention>();
Assert.IsType<CustomRoutingConvention>(routing);
}
[Fact]
public async Task AddRabbitMQ_ShouldRegisterNullOutboxStoreAndRetryPublisherByDefault()
{
var services = new ServiceCollection();
services.AddLogging();
var configuration = new ConfigurationBuilder()
.AddInMemoryCollection(new Dictionary<string, string?> { ["RabbitMQ:HostName"] = "h" })
.Build();
services.AddRabbitMQ(configuration);
await using var provider = services.BuildServiceProvider();
var store = provider.GetRequiredService<IMessageOutboxStore>();
Assert.IsType<NullMessageOutboxStore>(store);
var retryPublisher = provider.GetRequiredService<IMessageOutboxRetryPublisher>();
// NullMessageOutboxRetryPublisher 是 internal,仅做不为 null 校验
Assert.NotNull(retryPublisher);
Assert.Equal(
"Dpz.Core.MessageQueue.RabbitMQ.NullMessageOutboxRetryPublisher",
retryPublisher.GetType().FullName
);
// 默认 Null 实现的重试发布也应保持空操作语义
await retryPublisher.PublishRawAsync("ex", "rk", "id", "{}");
}
[Fact]
public void AddBatchTracking_ShouldRegisterBatchTracker()
{
var services = new ServiceCollection();
services.AddBatchTracking();
var descriptor = services.SingleOrDefault(x => x.ServiceType == typeof(IBatchTracker));
Assert.NotNull(descriptor);
Assert.Equal(ServiceLifetime.Singleton, descriptor.Lifetime);
Assert.Equal(typeof(DistributedBatchTracker), descriptor.ImplementationType);
}
[Fact]
public void AddMessageOutboxRetryWorker_ShouldRegisterRetryServiceAndHostedService()
{
var services = new ServiceCollection();
services.AddMessageOutboxRetryWorker();
var retryServiceDescriptor = services.SingleOrDefault(x =>
x.ServiceType == typeof(IMessageOutboxRetryService)
);
Assert.NotNull(retryServiceDescriptor);
Assert.Equal(ServiceLifetime.Singleton, retryServiceDescriptor.Lifetime);
Assert.Equal(typeof(MessageOutboxRetryService), retryServiceDescriptor.ImplementationType);
var hostedServiceDescriptor = services.LastOrDefault(x =>
x.ServiceType == typeof(IHostedService)
);
Assert.NotNull(hostedServiceDescriptor);
Assert.Equal(
typeof(MessageOutboxRetryBackgroundService),
hostedServiceDescriptor.ImplementationType
);
}
private sealed class TestMessage : MessageBase { }
private sealed class TestMessageHandler : IMessageHandler<TestMessage>
{
public Task<MessageHandlerResult> HandleAsync(
TestMessage message,
CancellationToken cancellationToken = default
)
{
return Task.FromResult(MessageHandlerResult.Ok());
}
}
private sealed class TestMessageResultHandler
: IMessageHandler<TestMessage, MessageHandlerResult<string>>
{
public Task<MessageHandlerResult<string>> HandleAsync(
TestMessage message,
CancellationToken cancellationToken = default
)
{
return Task.FromResult(MessageHandlerResult<string>.Ok("ok"));
}
}
private sealed class CustomRoutingConvention : IMessageRoutingConvention
{
public string GetExchangeName<TMessage>() => "custom-exchange";
public string GetQueueName<TMessage>() => "custom-queue";
public string GetRoutingKey<TMessage>() => "custom.routing";
public Enums.ExchangeType GetExchangeType<TMessage>() => Enums.ExchangeType.Fanout;
}
}
⚠⚠ 以下内容为AI分析的结果,请根据实际情况进行判断。
代码解释
这是一个针对 消息队列服务扩展方法 的单元测试类,主要测试 RabbitMQ 消息队列的依赖注入配置功能。
整体结构
该测试类 MessageQueueServiceExtensionsTests 包含多个测试用例,验证消息队列框架的各种扩展方法是否正确注册了所需的服务。
各测试方法详解
1. AddRabbitMQ_ShouldRegisterCoreServicesAndOptions
测试目标: 验证 AddRabbitMQ 扩展方法是否正确注册了核心服务和配置选项
测试内容:
- 从内存配置中加载 RabbitMQ 连接参数(主机名、端口、用户名、密码、虚拟主机)
- 验证
RabbitMQOptions配置对象是否正确注入 - 验证路由约定(
IMessageRoutingConvention)默认实现为DefaultMessageRoutingConvention - 验证连接工厂(
IRabbitMQConnectionFactory)注册为RabbitMQConnectionFactory - 验证消息发布器(
IMessagePublisher<TestMessage>)注册为RabbitMQPublisher<TestMessage>
2. AddMessageConsumer_ShouldRegisterHandlerAndHostedService
测试目标: 验证无返回值的消息消费者注册
测试内容:
- 调用
AddMessageConsumer<TestMessage, TestMessageHandler>() - 验证
IMessageHandler<TestMessage>接口注册为 Scoped 生命周期 - 验证实现类型为
TestMessageHandler - 验证后台服务
RabbitMQConsumerBackgroundService<TestMessage>被注册为IHostedService
3. AddMessageConsumerWithResult_ShouldRegisterHandlerAndHostedService
测试目标: 验证带返回值的消息消费者注册
测试内容:
- 调用泛型方法注册带结果类型
MessageHandlerResult<string>的消费者 - 验证
IMessageHandler<TestMessage, MessageHandlerResult<string>>接口注册 - 验证后台服务类型为
RabbitMQConsumerBackgroundServiceWithResult<TestMessage, MessageHandlerResult<string>>
4. AddCustomRoutingConvention_ShouldReplaceRoutingConvention
测试目标: 验证自定义路由约定的替换机制
测试内容:
- 注册自定义路由约定
CustomRoutingConvention - 验证依赖注入容器返回的实例类型正确
5. AddRabbitMQ_ShouldRegisterNullOutboxStoreAndRetryPublisherByDefault
测试目标: 验证默认情况下的 Outbox 模式实现
测试内容:
- 验证默认注册的
IMessageOutboxStore为空实现NullMessageOutboxStore - 验证默认的重试发布器为内部类
NullMessageOutboxRetryPublisher - 测试重试发布方法可安全调用(空操作)
设计意图: 采用空对象模式,未启用 Outbox 功能时不抛异常
6. AddBatchTracking_ShouldRegisterBatchTracker
测试目标: 验证批量处理追踪器的注册
测试内容:
- 验证
IBatchTracker接口注册为 Singleton 单例 - 实现类型为
DistributedBatchTracker
7. AddMessageOutboxRetryWorker_ShouldRegisterRetryServiceAndHostedService
测试目标: 验证 Outbox 重试工作器的注册
测试内容:
- 验证重试服务
IMessageOutboxRetryService注册为 Singleton - 实现类型为
MessageOutboxRetryService - 验证后台工作服务
MessageOutboxRetryBackgroundService被注册
辅助测试类
TestMessage
简单的测试消息类,继承自 MessageBase
TestMessageHandler
无返回值的消息处理器,返回 MessageHandlerResult.Ok()
TestMessageResultHandler
带返回值的消息处理器,返回 MessageHandlerResult<string>.Ok("ok")
CustomRoutingConvention
自定义路由约定实现,固定返回自定义的交换机、队列和路由键名称
总结
这个测试类全面覆盖了消息队列框架的依赖注入配置验证,包括:
- ✅ RabbitMQ 核心组件注册
- ✅ 消息消费者生命周期管理
- ✅ 自定义扩展点(路由约定)
- ✅ 可靠性保障机制(Outbox 模式、重试机制)
- ✅ 批量处理追踪
测试采用标准的 xUnit 框架,使用 ServiceCollection 模拟 ASP.NET Core 的依赖注入容器。
AI 正在分析代码…
评论加载中...