使用C#进行单元测试RabbitMQ推送 - .Net Core

Dev*_*per 5 c# unit-testing rabbitmq .net-core asp.net-core

我创建了一个.net核心API,它在RabbitMQ队列中推送一条消息.我曾经习惯IOptions.json文件中读取配置数据并将其添加为依赖项.

以下是我的控制器的代码:

[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();
    }
 }
Run Code Online (Sandbox Code Playgroud)

连接详细信息类具有以下属性

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

    public string UserName { get; set; }

    public string Password { get; set; }
}
Run Code Online (Sandbox Code Playgroud)

现在我想对它进行单元测试,但由于我要对黑盒子进行测试,我无法想到如何对它进行单元测试并寻求实际帮助.

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);
    }
}
Run Code Online (Sandbox Code Playgroud)

助手班

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;
    }
}
Run Code Online (Sandbox Code Playgroud)

Nko*_*osi 7

将控制器与实施问题紧密耦合使得很难在没有副作用的情况下测试控制器.从您提供的示例中,您已经证明您正在封装第三方API实现并仅显示抽象.好.但是,您还没有创建一个允许您在测试时模拟它们的抽象.我建议RabbitMQConnection允许这样做的重构.

首先有自己的支持抽象.

public interface IRabbitMQConnectionFactory {
    IConnection CreateConnection();
}
Run Code Online (Sandbox Code Playgroud)

并重构RabbitMQConnection如下

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;
    }
}
Run Code Online (Sandbox Code Playgroud)

花些时间仔细检查一下这个重构器的功能.该IOptions从控制器到工厂搬迁和RabbitMQConnection也被简化为做到这一点的预期目的.创建连接.

控制器现在也需要重构

[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);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

再次注意控制器的简化.现在,这允许在测试时对工厂进行模拟和注入,并且通过扩展允许工具使用模拟RabbitMQHelper.您可以使用您选择的模拟框架来实现依赖关系或纯DI.