Unit test RabbitMQ MessageProducer in an ASP.NET Core Web API.

huangapple go评论71阅读模式
英文:

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 =&gt; m.ExchangeDeclare(pubRequest.ExchangeName, pubRequest.ExchangeType.ToString().ToLower(), pubRequest.Durable, false, null));
}

Mock:

  internal class RabbitMQMoq
    {
        #region consts

        public const string EXCHANGE_NAME = &quot;TEST&quot;;
        public const string DLX_EXCHANGE_NAME = &quot;DLX_TEST&quot;;
        public const string QUEUE_NAME = &quot;TEST&quot;;
        public const string DLX_QUEUE_NAME = &quot;DLX_TEST&quot;;
        public const string ROUTING_KEY = &quot;TEST&quot;;
        public const string CONSUMER_TAG = &quot;1234&quot;;
        public const ExchangeType EXCHNAGE_TYPE = ExchangeType.Topic;

        #endregion

        #region properties
        public Mock&lt;IModel&gt; DefaultChannel { get; private set; }
        public Mock&lt;MockEventHandler&gt; EventHandler { get; private set; }
        public Dictionary&lt;string, IAsyncBasicConsumer&gt; Consumers { get; private set; }
        public MojRabbitMQBus Bus { get; private set; }

        #endregion

        #region c-tor

        public RabbitMQMoq(HostConfiguration hostConfiguration= null)
        {
            DefaultChannel = new Mock&lt;IModel&gt;();
            Consumers = new Dictionary&lt;string, IAsyncBasicConsumer&gt;();
            EventHandler = new Mock&lt;MockEventHandler&gt;();
            Bus = CreateRabbitMQBService(hostConfiguration ?? DefaultHostConfiguration);
        }

  public PublishExchangeRequest&lt;MockEvent&gt; Publish(MockEvent mockEvent, PublishExchangeRequest&lt;MockEvent&gt; request = null)
        {
            var pubRequest = request ?? CreatePublishExchangeRequest(mockEvent);

            Bus.Publish(pubRequest);

            return pubRequest;
        }

 private Mock&lt;IServiceScopeFactory&gt; CreateServiceScopeFactory()
        {
            var serviceProvider = new Mock&lt;IServiceProvider&gt;();
            serviceProvider
          .Setup(x =&gt; x.GetService(typeof(MockEventHandler)))
          .Returns(EventHandler.Object);
            var serviceScope = new Mock&lt;IServiceScope&gt;();
            serviceScope.Setup(x =&gt; x.ServiceProvider).Returns(serviceProvider.Object);
            var serviceScopeFactory = new Mock&lt;IServiceScopeFactory&gt;();
            serviceScopeFactory
                .Setup(x =&gt; x.CreateScope())
                .Returns(serviceScope.Object);
            return serviceScopeFactory;
        }
}

huangapple
  • 本文由 发表于 2023年2月23日 21:10:43
  • 转载请务必保留本文链接:https://go.coder-hub.com/75545303.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定