using System.Text;
using Dpz.Core.MessageQueue.Abstractions;
using Dpz.Core.MessageQueue.RabbitMQ;
using Microsoft.Extensions.Logging.Abstractions;
using Moq;
using RabbitMQ.Client;
namespace Dpz.Core.MessageQueue.Test.RabbitMQ;
/// <summary>
/// 验证 <see cref="RabbitMQRawPublisher"/> 的核心契约:
/// 调用连接工厂创建 Channel、按指定 exchange/routingKey 投递、设置消息属性、关闭 Channel。
/// </summary>
public class RabbitMQRawPublisherTests
{
[Fact]
public async Task PublishRawAsync_ShouldInvokeBasicPublishWithProvidedParameters()
{
var (channel, factory) = BuildChannelAndFactory();
var publisher = new RabbitMQRawPublisher(
factory.Object,
NullLogger<RabbitMQRawPublisher>.Instance
);
const string payload = "{\"foo\":\"bar\"}";
await publisher.PublishRawAsync("ex.test", "rk.test", "msg-123", payload);
factory.Verify(x => x.CreateChannelAsync(), Times.Once);
BasicProperties? captured = null;
ReadOnlyMemory<byte> capturedBody = default;
channel.Verify(
c =>
c.BasicPublishAsync(
"ex.test",
"rk.test",
false,
It.IsAny<BasicProperties>(),
It.IsAny<ReadOnlyMemory<byte>>(),
It.IsAny<CancellationToken>()
),
Times.Once
);
// 重新捕获以便断言 properties 字段
channel.Invocations.Clear();
channel
.Setup(c =>
c.BasicPublishAsync(
It.IsAny<string>(),
It.IsAny<string>(),
It.IsAny<bool>(),
It.IsAny<BasicProperties>(),
It.IsAny<ReadOnlyMemory<byte>>(),
It.IsAny<CancellationToken>()
)
)
.Callback<
string,
string,
bool,
BasicProperties,
ReadOnlyMemory<byte>,
CancellationToken
>(
(_, _, _, props, body, _) =>
{
captured = props;
capturedBody = body;
}
)
.Returns(ValueTask.CompletedTask);
await publisher.PublishRawAsync("ex.test", "rk.test", "msg-456", payload);
Assert.NotNull(captured);
Assert.Equal("msg-456", captured!.MessageId);
Assert.True(captured.Persistent);
Assert.Equal(DeliveryModes.Persistent, captured.DeliveryMode);
Assert.Equal("application/json", captured.ContentType);
Assert.Equal(payload, Encoding.UTF8.GetString(capturedBody.Span));
}
[Fact]
public async Task PublishRawAsync_ShouldDisposeChannelAfterPublish()
{
var (channel, factory) = BuildChannelAndFactory();
var publisher = new RabbitMQRawPublisher(
factory.Object,
NullLogger<RabbitMQRawPublisher>.Instance
);
await publisher.PublishRawAsync("ex", "rk", "id", "{}");
// RabbitMQRawPublisher 使用 await using 管理 Channel,调用结束应释放
channel.Verify(c => c.DisposeAsync(), Times.Once);
}
[Fact]
public async Task PublishRawAsync_ShouldPropagate_WhenBasicPublishThrows()
{
var (channel, factory) = BuildChannelAndFactory();
channel
.Setup(c =>
c.BasicPublishAsync(
It.IsAny<string>(),
It.IsAny<string>(),
It.IsAny<bool>(),
It.IsAny<BasicProperties>(),
It.IsAny<ReadOnlyMemory<byte>>(),
It.IsAny<CancellationToken>()
)
)
.ThrowsAsync(new InvalidOperationException("publish failed"));
var publisher = new RabbitMQRawPublisher(
factory.Object,
NullLogger<RabbitMQRawPublisher>.Instance
);
await Assert.ThrowsAsync<InvalidOperationException>(() =>
publisher.PublishRawAsync("ex", "rk", "id", "{}")
);
}
private static (
Mock<IChannel> channel,
Mock<IRabbitMQConnectionFactory> factory
) BuildChannelAndFactory()
{
var channel = new Mock<IChannel>();
channel
.Setup(c =>
c.BasicPublishAsync(
It.IsAny<string>(),
It.IsAny<string>(),
It.IsAny<bool>(),
It.IsAny<BasicProperties>(),
It.IsAny<ReadOnlyMemory<byte>>(),
It.IsAny<CancellationToken>()
)
)
.Returns(ValueTask.CompletedTask);
channel.Setup(c => c.DisposeAsync()).Returns(ValueTask.CompletedTask);
var factory = new Mock<IRabbitMQConnectionFactory>();
factory.Setup(f => f.CreateChannelAsync()).ReturnsAsync(channel.Object);
return (channel, factory);
}
}
⚠⚠ 以下内容为AI分析的结果,请根据实际情况进行判断。
这是一个针对 RabbitMQRawPublisher 类的单元测试文件。让我逐一解释这个测试类的结构和功能:
测试类概述
RabbitMQRawPublisherTests 类用于验证 RabbitMQRawPublisher 的核心功能契约,确保该类能够正确地:
- 创建 RabbitMQ 连接通道
- 按指定参数发布消息
- 设置正确的消息属性
- 正确关闭通道资源
主要测试方法
1. PublishRawAsync_ShouldInvokeBasicPublishWithProvidedParameters()
测试目标:验证发布方法是否正确调用底层 RabbitMQ API 并传递正确参数
测试流程:
// 1. 创建模拟对象
var (channel, factory) = BuildChannelAndFactory();
var publisher = new RabbitMQRawPublisher(factory.Object, NullLogger);
// 2. 调用发布方法
await publisher.PublishRawAsync("ex.test", "rk.test", "msg-123", payload);
// 3. 验证调用行为
factory.Verify(x => x.CreateChannelAsync(), Times.Once);
channel.Verify(c => c.BasicPublishAsync(...), Times.Once);
重点验证:
- 消息属性设置正确(MessageId、持久化、内容类型)
- 消息体编码正确(UTF-8)
- 交换器和路由键参数传递正确
2. PublishRawAsync_ShouldDisposeChannelAfterPublish()
测试目标:确保资源正确释放
await publisher.PublishRawAsync("ex", "rk", "id", "{}");
// 验证 Channel 被正确释放
channel.Verify(c => c.DisposeAsync(), Times.Once);
3. PublishRawAsync_ShouldPropagate_WhenBasicPublishThrows()
测试目标:验证异常处理机制
// 模拟底层方法抛出异常
channel.Setup(...).ThrowsAsync(new InvalidOperationException("publish failed"));
// 验证异常被正确传播
await Assert.ThrowsAsync<InvalidOperationException>(() =>
publisher.PublishRawAsync("ex", "rk", "id", "{}")
);
辅助方法
BuildChannelAndFactory()
创建测试所需的模拟对象:
private static (Mock<IChannel> channel, Mock<IRabbitMQConnectionFactory> factory) BuildChannelAndFactory()
{
// 创建 Channel 模拟对象,设置默认行为
var channel = new Mock<IChannel>();
channel.Setup(...).Returns(ValueTask.CompletedTask);
// 创建工厂模拟对象
var factory = new Mock<IRabbitMQConnectionFactory>();
factory.Setup(f => f.CreateChannelAsync()).ReturnsAsync(channel.Object);
return (channel, factory);
}
测试特色
- 使用 Moq 框架:创建模拟对象来隔离依赖
- 异步测试:所有测试方法都是异步的,符合现代 C# 最佳实践
- 行为验证:不仅测试结果,还验证方法调用次数和参数
- 资源管理测试:确保
await using模式正确工作 - 异常传播测试:验证错误处理机制
测试价值
这些测试确保了 RabbitMQRawPublisher 类:
- 正确集成 RabbitMQ 客户端库
- 遵循资源管理最佳实践
- 具有合适的错误处理机制
- 消息属性设置符合预期
这是一个结构良好的单元测试套件,遵循了测试驱动开发的最佳实践。
评论加载中...