英文:
Unit test RabbitMQ MessageProducer in an ASP.NET Core Web API
问题
I have setup a MessageProducer
with publisher confirms following the tutorial provided by RabbitMQ docs (https://www.rabbitmq.com/tutorials/tutorial-seven-dotnet.html).
I am using XUnit and NSubstitute for my tests, and I now struggle to unit-test the functionality of the callback function I've defined for the IModel _channel
.
If I just test that the channel received the expected amounts of BasicPublish()
the test runs to completion.
Here's my service method:
public async Task SendMessagesWithConfirmAsync<T>(IEnumerable<T> messages, string queueName, string routingKey)
{
_channel.QueueDeclare(queueName, true, false);
_channel.ConfirmSelect();
// Register callbacks to handle acknowledgments
_channel.BasicAcks += (sender, ea) => CleanOutstandingConfirms(ea.DeliveryTag, ea.Multiple);
_channel.BasicNacks += (sender, ea) =>
{
_outstandingConfirms.TryGetValue(ea.DeliveryTag, out var body);
Console.WriteLine(
$"Message with body {body} has been nack-ed. Sequence number: {ea.DeliveryTag}, multiple: {ea.Multiple}");
CleanOutstandingConfirms(ea.DeliveryTag, ea.Multiple);
};
foreach (var message in messages)
{
var body = JsonSerializer.Serialize(message);
_outstandingConfirms.TryAdd(_channel.NextPublishSeqNo, body);
_channel.BasicPublish(queueName, routingKey, null, Encoding.UTF8.GetBytes(body));
}
await Task.CompletedTask;
}
And in my unit test I want to make sure the callback events are correctly triggered
[Theory]
[InlineData("Test 1", "Test 2", "Test 3")]
public async void SendMessageWithConfirm_MultipleMessages_ShouldPublishMessagesAndWaitForConfirmOrDie(
params string[] messages)
{
// Arrange
var messageProducer = new RabbitMqMessageProducer(_connectionFactory);
// Act
await messageProducer.SendMessagesWithConfirmAsync(messages, "invitations", "invitation");
// Assert
_channel.Received(messages.Length).BasicPublish(Arg.Any<string>(), Arg.Any<string>(),
null, Arg.Any<ReadOnlyMemory<byte>>());
// Assert on callback events
// ...
}
Another thing is that the _channel.NextPublishSeqNo
never gets incremented.
Any help or derivation to the correct documentation here would be much appreciated.
英文:
I have setup a MessageProducer
with publisher confirms following the tutorial provided by RabbitMQ docs (https://www.rabbitmq.com/tutorials/tutorial-seven-dotnet.html).
I am using XUnit and NSubstitute for my tests, and I now struggle to unit-test the functionality of the callback function I've defined for the IModel _channel
.
If I just test that the channel received the expected amounts of BasicPublish()
the test runs to completion.
Here's my service method:
public async Task SendMessagesWithConfirmAsync<T>(IEnumerable<T> messages, string queueName, string routingKey)
{
_channel.QueueDeclare(queueName, true, false);
_channel.ConfirmSelect();
// Register callbacks to handle acknowledgments
_channel.BasicAcks += (sender, ea) => CleanOutstandingConfirms(ea.DeliveryTag, ea.Multiple);
_channel.BasicNacks += (sender, ea) =>
{
_outstandingConfirms.TryGetValue(ea.DeliveryTag, out var body);
Console.WriteLine(
$"Message with body {body} has been nack-ed. Sequence number: {ea.DeliveryTag}, multiple: {ea.Multiple}");
CleanOutstandingConfirms(ea.DeliveryTag, ea.Multiple);
};
foreach (var message in messages)
{
var body = JsonSerializer.Serialize(message);
_outstandingConfirms.TryAdd(_channel.NextPublishSeqNo, body);
_channel.BasicPublish(queueName, routingKey, null, Encoding.UTF8.GetBytes(body));
}
await Task.CompletedTask;
}
And in my unit test I want to make sure the callback events are correctly triggered
[Theory]
[InlineData("Test 1", "Test 2", "Test 3")]
public async void SendMessageWithConfirm_MultipleMessages_ShouldPublishMessagesAndWaitForConfirmOrDie(
params string[] messages)
{
// Arrange
var messageProducer = new RabbitMqMessageProducer(_connectionFactory);
// Act
await messageProducer.SendMessagesWithConfirmAsync(messages, "invitations", "invitation");
// Assert
_channel.Received(messages.Length).BasicPublish(Arg.Any<string>(), Arg.Any<string>(),
null, Arg.Any<ReadOnlyMemory<byte>>());
// Assert on callback events
// ...
}
Another thing is that the _channel.NextPublishSeqNo
never gets incremented.
Any help or derivation to the correct documentation here would be much appreciated.
答案1
得分: 2
以下是您提供的代码的翻译部分:
一个用于测试 rabbit 的单元测试不应该检查 rabbit 服务器。
对于 rabbitMQ 服务器,你应该添加集成测试。
在单元测试中,你检查你的代码,所以你必须添加模拟 rabbit MQ 服务器的模拟对象。
我在单元测试中使用了 Xunit NuGet 包,以及使用 Moq NuGet 包来模拟 rabbitMQ,就像这样:
单元测试:
```cs
[Fact]
public void publish__exchange_declare()
{
//准备
var rabbitMQMoq = new RabbitMQMoq();
//执行
var pubRequest = rabbitMQMoq.Publish(new MockEvent());
//断言
rabbitMQMoq.DefaultChannel.Verify(m => m.ExchangeDeclare(pubRequest.ExchangeName, pubRequest.ExchangeType.ToString().ToLower(), pubRequest.Durable, false, null));
}
模拟对象:
internal class RabbitMQMoq
{
#region 常量
public const string EXCHANGE_NAME = "TEST";
public const string DLX_EXCHANGE_NAME = "DLX_TEST";
public const string QUEUE_NAME = "TEST";
public const string DLX_QUEUE_NAME = "DLX_TEST";
public const string ROUTING_KEY = "TEST";
public const string CONSUMER_TAG = "1234";
public const ExchangeType EXCHNAGE_TYPE = ExchangeType.Topic;
#endregion
#region 属性
public Mock<IModel> DefaultChannel { get; private set; }
public Mock<MockEventHandler> EventHandler { get; private set; }
public Dictionary<string, IAsyncBasicConsumer> Consumers { get; private set; }
public MojRabbitMQBus Bus { get; private set; }
#endregion
#region 构造函数
public RabbitMQMoq(HostConfiguration hostConfiguration= null)
{
DefaultChannel = new Mock<IModel>();
Consumers = new Dictionary<string, IAsyncBasicConsumer>();
EventHandler = new Mock<MockEventHandler>();
Bus = CreateRabbitMQBService(hostConfiguration ?? DefaultHostConfiguration);
}
public PublishExchangeRequest<MockEvent> Publish(MockEvent mockEvent, PublishExchangeRequest<MockEvent> request = null)
{
var pubRequest = request ?? CreatePublishExchangeRequest(mockEvent);
Bus.Publish(pubRequest);
return pubRequest;
}
private Mock<IServiceScopeFactory> CreateServiceScopeFactory()
{
var serviceProvider = new Mock<IServiceProvider>();
serviceProvider
.Setup(x => x.GetService(typeof(MockEventHandler)))
.Returns(EventHandler.Object);
var serviceScope = new Mock<IServiceScope>();
serviceScope.Setup(x => x.ServiceProvider).Returns(serviceProvider.Object);
var serviceScopeFactory = new Mock<IServiceScopeFactory>();
serviceScopeFactory
.Setup(x => x.CreateScope())
.Returns(serviceScope.Object);
return serviceScopeFactory;
}
}
请注意,翻译中尽量保持了代码的结构和格式,以便您能够更容易地理解翻译后的内容。
英文:
A unit test for rabbit should not check the rabbit server at all.
FOr rabbitMQ server you should add integration tests.
In unit test, you check YOUR code, so you have to add mocks that mock the rabbit MQ server.
I used Xunit nuget for the unit tests, and moq nuget for mocking the rabbitMQ like so:
Unit Test:
[Fact]
public void publish__exchange_declare()
{
//Arange
var rabbitMQMoq = new RabbitMQMoq();
//Act
var pubRequest = rabbitMQMoq.Publish(new MockEvent());
//Assert
rabbitMQMoq.DefaultChannel.Verify(m => m.ExchangeDeclare(pubRequest.ExchangeName, pubRequest.ExchangeType.ToString().ToLower(), pubRequest.Durable, false, null));
}
Mock:
internal class RabbitMQMoq
{
#region consts
public const string EXCHANGE_NAME = "TEST";
public const string DLX_EXCHANGE_NAME = "DLX_TEST";
public const string QUEUE_NAME = "TEST";
public const string DLX_QUEUE_NAME = "DLX_TEST";
public const string ROUTING_KEY = "TEST";
public const string CONSUMER_TAG = "1234";
public const ExchangeType EXCHNAGE_TYPE = ExchangeType.Topic;
#endregion
#region properties
public Mock<IModel> DefaultChannel { get; private set; }
public Mock<MockEventHandler> EventHandler { get; private set; }
public Dictionary<string, IAsyncBasicConsumer> Consumers { get; private set; }
public MojRabbitMQBus Bus { get; private set; }
#endregion
#region c-tor
public RabbitMQMoq(HostConfiguration hostConfiguration= null)
{
DefaultChannel = new Mock<IModel>();
Consumers = new Dictionary<string, IAsyncBasicConsumer>();
EventHandler = new Mock<MockEventHandler>();
Bus = CreateRabbitMQBService(hostConfiguration ?? DefaultHostConfiguration);
}
public PublishExchangeRequest<MockEvent> Publish(MockEvent mockEvent, PublishExchangeRequest<MockEvent> request = null)
{
var pubRequest = request ?? CreatePublishExchangeRequest(mockEvent);
Bus.Publish(pubRequest);
return pubRequest;
}
private Mock<IServiceScopeFactory> CreateServiceScopeFactory()
{
var serviceProvider = new Mock<IServiceProvider>();
serviceProvider
.Setup(x => x.GetService(typeof(MockEventHandler)))
.Returns(EventHandler.Object);
var serviceScope = new Mock<IServiceScope>();
serviceScope.Setup(x => x.ServiceProvider).Returns(serviceProvider.Object);
var serviceScopeFactory = new Mock<IServiceScopeFactory>();
serviceScopeFactory
.Setup(x => x.CreateScope())
.Returns(serviceScope.Object);
return serviceScopeFactory;
}
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论