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<bool> HandleAsync(
TestMessage message,
CancellationToken cancellationToken = default
)
{
return Task.FromResult(true);
}
}
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 Dpz.Core.MessageQueue.Enums.ExchangeType GetExchangeType<TMessage>() =>
Dpz.Core.MessageQueue.Enums.ExchangeType.Fanout;
}
}
⚠⚠ 以下内容为AI分析的结果,请根据实际情况进行判断。
这是一个用于测试消息队列服务扩展方法的单元测试类。该类主要测试了 RabbitMQ 消息队列系统中各种依赖注入和服务注册功能。让我详细解释一下每个测试方法:
主要测试内容
1. AddRabbitMQ_ShouldRegisterCoreServicesAndOptions 测试
[Fact]
public async Task AddRabbitMQ_ShouldRegisterCoreServicesAndOptions()
功能:测试 RabbitMQ 核心服务和配置选项的注册
- 创建内存配置,包含 RabbitMQ 连接参数(主机名、端口、用户名、密码、虚拟主机)
- 调用
AddRabbitMQ()扩展方法注册服务 - 验证配置选项是否正确注入
- 验证核心服务是否正确注册:
IMessageRoutingConvention→DefaultMessageRoutingConventionIRabbitMQConnectionFactory→RabbitMQConnectionFactoryIMessagePublisher<TestMessage>→RabbitMQPublisher<TestMessage>
2. AddMessageConsumer_ShouldRegisterHandlerAndHostedService 测试
[Fact]
public void AddMessageConsumer_ShouldRegisterHandlerAndHostedService()
功能:测试消息消费者的注册(无返回值处理器)
- 注册消息处理器
TestMessageHandler - 验证
IMessageHandler<TestMessage>服务注册为 Scoped 生命周期 - 验证后台服务
RabbitMQConsumerBackgroundService<TestMessage>被注册为IHostedService
3. AddMessageConsumerWithResult_ShouldRegisterHandlerAndHostedService 测试
[Fact]
public void AddMessageConsumerWithResult_ShouldRegisterHandlerAndHostedService()
功能:测试带返回结果的消息消费者注册
- 注册带返回值的消息处理器
TestMessageResultHandler - 验证
IMessageHandler<TestMessage, MessageHandlerResult<string>>服务注册 - 验证对应的后台服务
RabbitMQConsumerBackgroundServiceWithResult被注册
4. AddCustomRoutingConvention_ShouldReplaceRoutingConvention 测试
[Fact]
public void AddCustomRoutingConvention_ShouldReplaceRoutingConvention()
功能:测试自定义路由约定的注册
- 注册自定义路由约定
CustomRoutingConvention - 验证默认路由约定被替换为自定义实现
5. AddRabbitMQ_ShouldRegisterNullOutboxStoreAndRetryPublisherByDefault 测试
[Fact]
public async Task AddRabbitMQ_ShouldRegisterNullOutboxStoreAndRetryPublisherByDefault()
功能:测试默认的空实现服务注册
- 验证默认注册了
NullMessageOutboxStore(空实现的消息发件箱存储) - 验证默认注册了
NullMessageOutboxRetryPublisher(空实现的重试发布器) - 测试空实现的重试发布方法确实不执行任何操作
6. AddBatchTracking_ShouldRegisterBatchTracker 测试
[Fact]
public void AddBatchTracking_ShouldRegisterBatchTracker()
功能:测试批处理跟踪器的注册
- 验证
IBatchTracker服务注册为DistributedBatchTracker单例实现
7. AddMessageOutboxRetryWorker_ShouldRegisterRetryServiceAndHostedService 测试
[Fact]
public void AddMessageOutboxRetryWorker_ShouldRegisterRetryServiceAndHostedService()
功能:测试消息发件箱重试工作器的注册
- 验证
IMessageOutboxRetryService注册为单例 - 验证
MessageOutboxRetryBackgroundService注册为后台服务
测试辅助类
代码还定义了几个测试用的辅助类:
TestMessage:继承自MessageBase的测试消息类TestMessageHandler:实现IMessageHandler<TestMessage>的简单处理器TestMessageResultHandler:实现带返回值的消息处理器CustomRoutingConvention:自定义路由约定实现,返回固定的交换机名、队列名和路由键
总结
这个测试类全面验证了消息队列框架的依赖注入扩展方法,确保:
- 配置正确绑定
- 服务正确注册和生命周期管理
- 默认实现和自定义实现都能正常工作
- 后台服务正确注册
- 各种消息处理模式都得到支持
这是一个典型的基于 xUnit 的单元测试,用于验证 .NET 依赖注入容器中服务注册的正确性。
评论加载中...