【问题标题】:Unit test RabbitMQ push with C# - .Net Core使用 C# 进行单元测试 RabbitMQ 推送 - .Net Core
【发布时间】:2017-10-26 19:25:56
【问题描述】:

我创建了一个 .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();
    }
 }

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

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

    public string UserName { get; set; }

    public string Password { get; set; }
}

现在我想对它进行单元测试,但由于我要针对黑盒进行测试,所以我无法考虑如何对其进行单元测试并寻求帮助。

连接类

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);
    }
}

助手类

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;
    }
}

【问题讨论】:

  • 您希望在这些测试中编写什么样的断言?
  • 由于我正在针对黑盒进行测试,因此我不确定哪个断言会对我有所帮助。 IsEqual 绝对不会有帮助。但是,如果我可以显示消息计数增加或插入时有下一步操作,那可能会有所帮助。
  • 您需要测试控制器是否返回正确的IActionResult,然后检查是否将正确的消息推送到队列中。
  • @ParthoGanguly 抽象,抽象,抽象。您正在尝试测试将其更改为集成测试的实现问题。封装可以模拟的抽象背后的实现问题,以便您可以单独进行单元测试。
  • @ParthoGanguly 你用的是什么版本的 RabbitMQ?

标签: c# unit-testing asp.net-core .net-core rabbitmq


【解决方案1】:

将您的控制器与实现问题紧密耦合会导致难以在没有副作用的情况下测试您的控制器。从您提供的示例中,您已经表明您正在封装第 3 部分 API 实现并且仅公开抽象。好的。但是,您尚未创建允许您在测试时模拟它们的抽象。我建议重构 RabbitMQConnection 以实现这一点。

首先要有你自己的支持抽象。

public interface IRabbitMQConnectionFactory {
    IConnection CreateConnection();
}

并重构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;
    }
}

花点时间仔细回顾一下这个重构做了什么。 IOptions 已从控制器移至工厂,RabbitMQConnection 也已简化以达到预期目的。创建连接。

Controller 现在也需要重构

[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);
        }
    }
}

再次注意控制器的简化。这现在允许在测试时模拟和注入工厂,并且通过扩展允许 RabbitMQHelper 使用模拟。您可以将选择的模拟框架用于依赖项或纯 DI。

【讨论】:

  • 我会检查并更新帖子。非常感谢您的指点。
  • 修改了代码,现在看起来好多了。现在模拟是可行的。此外,没有可用的连接对象和模型对象。这样做不好
  • @ParthoGanguly 很高兴为您提供帮助。
  • 如何传递连接细节?
【解决方案2】:

我不认为这是一个单元测试场景。如果您想使用外部组件(即数据库或消息队列)进行测试,那么我建议您将其作为集成测试进行。

我们要做的是拥有一个带有组件 SQL 数据库和 azure 消息总线的沙盒环境。我们有代码可以正确设置该组件的状态,即为数据库播种并清除消息总线。然后我们在环境上运行测试并检查数据库的状态或消息总线计数等。

【讨论】:

  • 不,首先您必须编写单元测试,以确保使用正确的参数调用特定方法,集成测试是另一个应该测试的级别
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2023-01-04
  • 2019-10-25
  • 1970-01-01
  • 2023-02-24
  • 2017-04-13
  • 2018-01-06
  • 2012-03-02
相关资源
最近更新 更多