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;
}
}
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With