【问题标题】:Unit Test using MOQ Nested Class & Interface C#使用 MOQ 嵌套类和接口 C# 进行单元测试
【发布时间】:2017-11-09 09:14:27
【问题描述】:

您好,我有以下接口及其实现。现在,我想对Send() 方法进行单元测试,该方法实际上会将消息推送到队列中。 由于我是 MoQ 的新手,不知道如何完成它。

public interface IAdapter 
{
    IChannel UseQueue(QueueDetail queueDetail);
}
public interface IChannel
{
    void Send(string key, byte[] message);
}

public class AdapternServiceBus : IAdapter
{   
    readonly IConnection connection;
    readonly IModel channel;

    public AdapternServiceBus(IConnection connection, IModel channel)
    {
        this.connection = connection;     
        this.channel = channel;
    }

    public IChannel BindAndUseQueue(QueueDetail queueDetail)
    {
        // Logic of creating and binding queue
        return new ServiceBusChannel(this, queueDetail.QueueName);
    }

    public IModel GetChannel()
    {
        return channel;
    }
}

public class ServiceBusChannel : IChannel
{
    readonly string containerName;
    IModel channel;

    public ServiceBusChannel(AdapternServiceBus adapter, string containerName)
    {   
        this.containerName = containerName;
        channel = adapter.GetChannel();
    }

    public void Send(string key, byte[] message)
    {
        // Publish the message
        channel.BasicPublish(exchangeName, key, null, message);
    }
}

在这里通过工厂,我们决定需要连接哪种类型的框架,在工厂内,我们打开一个传递给下一个类的连接,以便可以使用它来创建和绑定队列。 IChannel 的实现是主类,将用于与队列和主题进行实际对话。

【问题讨论】:

标签: c# unit-testing moq


【解决方案1】:

您想在ServiceBusChannel 类中测试Send 方法。

因此,在您的测试中,您应该实例化这个实际的具体类。

要实例化ServiceBusChannel,您需要一个AdapterServiceBus,它允许您设置channel 变量。

这里的问题是你依赖于一个具体的 AdapterServiceBus 类,所以你不能模拟它。您应该依赖一个 interface 来公开您想要模拟的实际行为。

类似 IAdpaterServiceBus 声明 GetChannel();(EDIT) 或将 GetChannel() 声明为现有 IAdapter 交互的方法)。 p>

然后你可以模拟这个:

var mockedAdapter = new Mock<IAdapterServiceBus>();

var mockedAdapter = new Mock<IAdapter>(); // if you added GetChannel() to IAdapter

然后模拟 GetChannel() 的行为:

mockedAdapter.Setup(x => x.GetChannel())
     .Returns( /* some mocked string value for your channel */);

然后您可以将其传递给您的 ServiceBusChannel 构造函数(经过修改使其接受抽象的IAdapterServiceBus IAdapter)而不是具体实例)

var service = new ServiceBusChannel(mockedAdapter.Object, containerName);

【讨论】:

  • 这就是为什么在 OP 澄清问题之前我没有提供答案,因为它不完整。因此,我的 cmets 首先并等待响应。我完全同意你关于抽象的建议。
  • @Nkosi - 所以,GetChannel() 必须在界面中? GetChannel() 的原因不在接口中,因为不同的消息传递平台可能有不同的创建通道的方式,因此它只是实现的一部分。让我知道你的观点。
【解决方案2】:

下面的最小示例展示了如何通过提供必要的依赖项来对 ServiceBusChannel.Send 方法进行单元测试,以使测试能够完成。它基于原始问题提供的示例。

[TestClass]
public class ServiceBusChannel_Should {
    [TestMethod]
    public void SendMessage() {
        //Arrange
        var channel = Mock.Of<IModel>();
        var connection = Mock.Of<IConnection>();
        var adapter = new AdapternServiceBus(connection, channel);
        var key = "somekey";
        var message = Encoding.UTF8.GetBytes("Hello World");
        var subject = new ServiceBusChannel(adapter, "containerName");

        //Act
        subject.Send(key, message);

        //Assert
        Mock.Get(channel).Verify(_ => _.BasicPublish(It.IsAny<string>(), 
                                         It.IsAny<string>(), 
                                         It.IsAny<bool>(), 
                                         It.IsAny<IBasicProperties>(), 
                                         It.IsAny<byte[]>())
                                  , Times.AtLeastOnce());
    }
}

注意:Moq 框架用于模拟抽象依赖项。他们的Quickstart 提供了如何使用该框架的示例。

虽然我认为被测对象不应与具体物耦合,但AdapternServiceBus 可以用作模拟依赖项的入口点,如上所示。您可以修改以满足您的特定需求,因为我假设提供的代码不是实际使用的代码,而是一个示例。

【讨论】:

  • 非常感谢您的指导。我已经通过这个 Mock.Get(channel).Verify(_ => _.BasicPublish("", key, null, default(byte[])), Times.AtLeastOnce()); 修改了资产。代码并得到“NotSupportedException - 扩展方法验证无效:_ => _.BasicPublish("", .key, null, null)"
  • 向队列发布消息的方法是BasicPublish(exchangeName, key, null, message);
  • @Developer 这就是我要求提供实际代码的原因。以免猜到要回答什么。您能否更新问题,以便我为您提供准确的帮助。
  • @Developer,Moq 也不适用于扩展方法。您需要找出扩展方法在内部调用的内容并对其进行模拟。
  • 已使用实际代码更新了 Send() 方法。此外,它看起来像是一种扩展方法。 :(
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-12-24
  • 2012-11-27
  • 2017-04-12
  • 2013-06-26
  • 1970-01-01
相关资源
最近更新 更多