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() 扩展方法注册服务
  • 验证配置选项是否正确注入
  • 验证核心服务是否正确注册:
    • IMessageRoutingConventionDefaultMessageRoutingConvention
    • IRabbitMQConnectionFactoryRabbitMQConnectionFactory
    • IMessagePublisher<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 注册为后台服务

测试辅助类

代码还定义了几个测试用的辅助类:

  1. TestMessage:继承自 MessageBase 的测试消息类
  2. TestMessageHandler:实现 IMessageHandler<TestMessage> 的简单处理器
  3. TestMessageResultHandler:实现带返回值的消息处理器
  4. CustomRoutingConvention:自定义路由约定实现,返回固定的交换机名、队列名和路由键

总结

这个测试类全面验证了消息队列框架的依赖注入扩展方法,确保:

  • 配置正确绑定
  • 服务正确注册和生命周期管理
  • 默认实现和自定义实现都能正常工作
  • 后台服务正确注册
  • 各种消息处理模式都得到支持

这是一个典型的基于 xUnit 的单元测试,用于验证 .NET 依赖注入容器中服务注册的正确性。

评论加载中...