Home > Software design >  Unit test running forever for call back method in C#
Unit test running forever for call back method in C#

Time:02-10

Following is the method to receive the message on RabbitMq Queue. Method works fine but the unit testing this method has problem mentioned below

      public void GetMessage(Action<string> action)
    {
        //create a connection factory
        var factory = new ConnectionFactory();

        if (_connectionString != null)
        {
            //assign connection string of rabbitmq
            factory.Uri = new Uri(_connectionString);
        }

        //create connection and channel from connectionfactory
        using (var connection = factory.CreateConnection())
        using (var channel = connection.CreateModel())
        {

            //declare the queque on the channel
            channel.QueueDeclare(queue: _queque,
                                 durable: false,
                                 exclusive: false,
                                 autoDelete: false,
                                 arguments: null);

            //create consumer using the channel
            var consumer = new EventingBasicConsumer(channel);
            consumer.Received  = (model, ea) =>
            {
                //When message is recieved on the consumer this will be triggered
                var body = ea.Body.ToArray();
                _message = Encoding.UTF8.GetString(body);

                //call the method passed by the caller method
                action(_message);
            };

            //add the consumer on this channel
            channel.BasicConsume(queue: _queque,
                                 autoAck: true,
                                 consumer: consumer);

            Console.ReadLine();
        }

    }

How to write a unit test test above message. Below unit test loading forever inside the call back method and the execution never goes to Assert.Equal line.

      [Fact]
    public async Task AddMessageAsync_ShouldAddToQueue()
    {
        //Arrange
        string messageToSend = "Test";
        string recievedMessage = null;
        var queue = GetQueueManager();

        queue.Message = messageToSend;

        await queue.AddMessageAsync();

        //Act
        queue.GetMessage((string message) =>
        {
            //Hit here and recieved the message but does not exist from this method
            recievedMessage = message;
        });

        //Assert
        Assert.Equal(messageToSend, recievedMessage);
    }

CodePudding user response:

One way to adress the issue would be a synchronization barrier. Here is an example: (There may be more efficient ways, though.)

... // Your code with some changes:
//create connection and channel from connectionfactory
        using (var connection = factory.CreateConnection())
        using (var channel = connection.CreateModel())
        {

            //declare the queque on the channel
            channel.QueueDeclare(queue: _queque,
                                 durable: false,
                                 exclusive: false,
                                 autoDelete: false,
                                 arguments: null);
            
            // Set up a synchronization barrier.
            using (var barrier = new ManualResetEventSlim(false))
            {

                //create consumer using the channel
                var consumer = new EventingBasicConsumer(channel);
                consumer.Received  = (model, ea) =>
                {
                //When message is recieved on the consumer this will be triggered
                    var body = ea.Body.ToArray();
                    _message = Encoding.UTF8.GetString(body);

                    try // you don't want "injected" code to break yours.
                    {
                        //call the method passed by the caller method
                        action(_message);
                    }
                    catch(Exception)
                    {
                        // at least log it!
                    }
                    finally
                    {
                        barrier.Set(); // Signal Event fired.
                    }
                };

                //add the consumer on this channel
                channel.BasicConsume(queue: _queque,
                                     autoAck: true,
                                     consumer: consumer);
 
                barrier.Wait(); // Wait for Event to fire.
            }
        }

I would imagine that the RabbitMQ API has had an update to support async/await instead of event-based? If so, then using that would of course be preferable.

If not: You may also want to explore Tasks and the Event-based Asynchronous Pattern (EAP)

  • Related