Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Unit test RabbitMQ push with C# - .Net Core

I have created a .net core API, which pushes a message in RabbitMQ queue. I have used IOptions to read configuration data from .json file and added it as dependency.

Below is the code of my controller:

[Route("api/[controller]")]
public class RestController : Controller
{
    private RabbitMQConnectionDetail _connectionDetail;

    public RestController(IOptions<RabbitMQConnectionDetail> connectionDetail)
    {
        _connectionDetail = connectionDetail.Value;
    }

    [HttpPost]
    public IActionResult Push([FromBody] OrderItem orderItem)
    {
        try
        {
            using (var rabbitMQConnection = new RabbitMQConnection(_connectionDetail.HostName,
                _connectionDetail.UserName, _connectionDetail.Password))
            {
                using (var connection = rabbitMQConnection.CreateConnection())
                {
                    var model = connection.CreateModel();
                    var helper = new RabbitMQHelper(model, "Topic_Exchange");
                    helper.PushMessageIntoQueue(orderItem.Serialize(), "Order_Queue");
                }
            }
        }
        catch (Exception)
        {
            return StatusCode((int)HttpStatusCode.BadRequest);
        }
        return Ok();
    }
 }

Connection details class has the below properties

public class RabbitMQConnectionDetail
{
    public string HostName { get; set; }

    public string UserName { get; set; }

    public string Password { get; set; }
}

Now I want to unit test it, but since I am going to test it against a blackbox, I'm not able to think of how to unit test it and looking for kind help.

ConnectionClass

public class RabbitMQConnection : IDisposable
{   
    private static IConnection _connection;
    private readonly string _hostName;
    private readonly string _userName;
    private readonly string _password;

    public RabbitMQConnection(string hostName, string userName, string password)
    {
        _hostName = hostName;
        _userName = userName;
        _password = password;
    }

    public IConnection CreateConnection()
    {
        var _factory = new ConnectionFactory
        {
            HostName = _hostName,
            UserName = _userName,
            Password = _password
        };
        _connection = _factory.CreateConnection();
        var model = _connection.CreateModel();

        return _connection;
    }

    public void Close()
    {
        _connection.Close();
    }

    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    protected virtual void Dispose(bool disposing)
    {
        if (disposing)
        {
            _connection.Close();
        }
    }

    ~ RabbitMQConnection()
    {
        Dispose(false);
    }
}

Helper class

public class RabbitMQHelper
{
    private static IModel _model;
    private static string _exchangeName;
    const string RoutingKey = "dummy-key.";

    public RabbitMQHelper(IModel model, string exchangeName)
    {
        _model = model;
        _exchangeName = exchangeName;
    }


    public void SetupQueue(string queueName)
    {
        _model.ExchangeDeclare(_exchangeName, ExchangeType.Topic);
        _model.QueueDeclare(queueName, true, false, false, null);
        _model.QueueBind(queueName, _exchangeName, RoutingKey);
    }

    public void PushMessageIntoQueue(byte[] message, string queue)
    {
        SetupQueue(queue);
        _model.BasicPublish(_exchangeName, RoutingKey, null, message);
    }

    public byte[] ReadMessageFromQueue(string queueName)
    {
        SetupQueue(queueName);
        byte[] message;
        var data = _model.BasicGet(queueName, false);
        message = data.Body;
        _model.BasicAck(data.DeliveryTag, false);
        return message;
    }
}
like image 264
Developer Avatar asked Oct 26 '17 19:10

Developer


1 Answers

Tightly coupling your Controller to implementation concerns are making it difficult to test your controller without side-effects. From the sample you provided you have shown that you are encapsulating the 3rd part API implementations and only exposing abstractions. Good. You however have not created an abstraction that would allow you to mock them when testing. I suggest a refactor of the RabbitMQConnection to allow for this.

First have your own backing abstraction.

public interface IRabbitMQConnectionFactory {
    IConnection CreateConnection();
}

And refactor RabbitMQConnection as follows

public class RabbitMQConnection : IRabbitMQConnectionFactory {
    private readonly RabbitMQConnectionDetail connectionDetails;

    public RabbitMQConnection(IOptions<RabbitMQConnectionDetail> connectionDetails) {
        this.connectionDetails = connectionDetails.Value;
    }

    public IConnection CreateConnection() {
        var factory = new ConnectionFactory {
            HostName = connectionDetails.HostName,
            UserName = connectionDetails.UserName,
            Password = connectionDetails.Password
        };
        var connection = factory.CreateConnection();
        return connection;
    }
}

Take some time and review exactly what was done with this refactor. The IOptions was moved from the Controller to the factory and the RabbitMQConnection has also been simplified to do it's intended purpose. Creating a connection.

The Controller now would need to be refactored as well

[Route("api/[controller]")]
public class RestController : Controller {
    private readonly IRabbitMQConnectionFactory factory;

    public RestController(IRabbitMQConnectionFactory factory) {
        this.factory = factory;
    }

    [HttpPost]
    public IActionResult Push([FromBody] OrderItem orderItem) {
        try {                
            using (var connection = factory.CreateConnection()) {
                var model = connection.CreateModel();
                var helper = new RabbitMQHelper(model, "Topic_Exchange");
                helper.PushMessageIntoQueue(orderItem.Serialize(), "Order_Queue");
                return Ok();
            }
        } catch (Exception) {
            //TODO: Log error message
            return StatusCode((int)HttpStatusCode.BadRequest);
        }
    }
}

Again note the simplification of the controller. This now allows the factory to be mocked and injected when testing and by extension allows the mocks to be used by the RabbitMQHelper. You can use your mocking framework of choice for dependencies or pure DI.

like image 179
Nkosi Avatar answered Sep 28 '22 18:09

Nkosi