简体   繁体   English

如何在单元测试中moq一个NetworkStream?

[英]How to moq a NetworkStream in a unit test?

I'm using Moq & NUnit as a unit test framework. 我正在使用Moq和NUnit作为单元测试框架。

I've written a method that is given a NetworkStream object as a parameter: 我写了一个方法,给出一个NetworkStream对象作为参数:

public static void ReadDataIntoBuffer(NetworkStream networkStream, Queue dataBuffer)
{
  if ((networkStream != null) && (dataBuffer != null))
  {
     while (networkStream.DataAvailable)
     {
        byte[] tempBuffer = new byte[512];

        // read the data from the network stream into the temporary buffer
        Int32 numberOfBytesRead = networkStream.Read(tempBuffer, 0, 512);

        // move all data into the main buffer
        for (Int32 i = 0; i < numberOfBytesRead; i++)
        {
           dataBuffer.Enqueue(tempBuffer[i]);
        }
     }
  } 
  else
  {
     if (networkStream != null)
     {
        throw new ArgumentNullException("networkStream");
     }

     if (dataBuffer != null)
     {
        throw new ArgumentNullException("dataBuffer");
     }
  }
}

Now I am looking at re-writing my unit tests for this method since the previously written tests rely on real NetworkStream objects and are not very nice to handle. 现在我正在考虑为这个方法重新编写单元测试,因为之前编写的测试依赖于真正的NetworkStream对象,并且不是很好处理。

How can I mock the NetworkStream? 我该如何模拟NetworkStream? I'm using Moq as mentioned beforehand. 我之前正在使用Moq。 Is it possible at all? 有可能吗? If not how could I workaround this problem? 如果不是,我怎么能解决这个问题?

Looking forward to your feedback! 期待您的反馈意见!

Here is the previous solution: 这是以前的解决方案:

public static void ReadDataIntoBuffer(Stream dataStream, Queue dataBuffer)
{
  if ((networkStream != null) && (dataBuffer != null))
  {
     byte[] tempBuffer = new byte[512];
     Int32 numberOfBytesRead = 0;

     // read the data from the network stream into the temporary buffer
     while ((numberOfBytesRead = dataStream.Read(tempBuffer, 0, 512) > 0)
     {
        // move all data into the main buffer
        for (Int32 i = 0; i < numberOfBytesRead; i++)
        {
           dataBuffer.Enqueue(tempBuffer[i]);
        }
     }
  } 
  else ...
}

UPDATE: 更新:

I've re-written my class once again. 我再次重写了我的课程。 Unit testing using the previous solution went fine but the real-world application example showed me why it is NOT possible for me to use the (otherwise great) suggestion of passing a Stream object into my method. 使用之前的解决方案进行单元测试很顺利,但现实世界的应用程序示例向我展示了为什么我不可能使用(否则很棒)将Stream对象传递给我的方法的建议。

First off, my application relies on a constant TCP connection. 首先,我的应用程序依赖于常量的TCP连接。 If you use Stream.Read (which is possible) and there is no data to receive it will block the execution. 如果您使用Stream.Read (可能)并且没有数据要接收它将阻止执行。 If you specify a timeout an exception will be thrown if no data is received. 如果指定超时,则在未收到任何数据时将引发异常。 This kind of behaviour is not acceptable for the (rather simple) application I need. 对于我需要的(相当简单的)应用程序,这种行为是不可接受的。 I just need a no-frills, constant TCP connection. 我只需要一个简单的,连续的TCP连接。 Therefore having the NetworkStream.DataAvailable property is paramount to my implementation. 因此,拥有NetworkStream.DataAvailable属性对我的实现至关重要。

The current solution: 目前的解决方案:

I ended up writing an interface and a wrapper to NetworkStream. 我最终为NetworkStream编写了一个接口和一个包装器。 I also ended up passing the byte array for the temporary receive buffer into the method. 我还最终将临时接收缓冲区的字节数组传递给方法。 Unit testing it now works rather well. 单元测试现在运行得相当好。

public static void ReadDataIntoBuffer(INetworkStream networkStream, Queue dataBuffer, byte[] tempRXBuffer)
{
    if ((networkStream != null) && (dataBuffer != null) && (tempRXBuffer != null))
    {
        // read the data from the network stream into the temporary buffer
        while(networkStream.DataAvailable)
        {
            Int32 numberOfBytesRead = networkStream.Read(tempRXBuffer, 0, tempRXBuffer.Length);

            // move all data into the main buffer
            for (Int32 i = 0; i < numberOfBytesRead; i++)
            {
                dataBuffer.Enqueue(tempRXBuffer[i]);
            }
        }
    }
    else ...
}

And here's the unit test that I use: 这是我使用的单元测试:

public void TestReadDataIntoBuffer()
{
    var networkStreamMock = new Mock<INetworkStream>();
    StringBuilder sb = new StringBuilder();

    sb.Append(_testMessageConstant1);
    sb.Append(_testMessageConstant2);
    sb.Append(_testMessageConstant3);
    sb.Append(_testMessageConstant4);
    sb.Append(_testMessageConstant5);


    // ARRANGE
    byte[] tempRXBuffer = Encoding.UTF8.GetBytes(sb.ToString());

    // return true so that the call to Read() is made
    networkStreamMock.Setup(x => x.DataAvailable).Returns(true);

    networkStreamMock.Setup(x => x.Read(It.IsAny<byte[]>(), It.IsAny<int>(), It.IsAny<int>())).Callback(() =>
        {
            // after the call to Read() re-setup the property so that we
            // we exit the data reading loop again
            networkStreamMock.Setup(x => x.DataAvailable).Returns(false);

        }).Returns(tempRXBuffer.Length);

    Queue resultQueue = new Queue();

    // ACT
    ReadDataIntoBuffer(networkStreamMock.Object, resultQueue, tempRXBuffer);

    // ASSERT
    Assert.AreEqual(Encoding.UTF8.GetBytes(sb.ToString()), resultQueue.ToArray());
}

You cannot mock the NetworkStream with moq since it is not an abstract class or an interface. 您不能使用moq模拟NetworkStream ,因为它不是抽象类或接口。 You can however create an abstraction on top of it and change your method to accept an instance of that abstraction. 但是,您可以在其上创建抽象并更改您的方法以接受该抽象的实例。 It could be something like this: 它可能是这样的:

public interface IMyNetworkStream
{
    int Read([In, Out] byte[] buffer, int offset, int size);
    bool DataAvailable {get;}
}

Now you create a class that implements the interface: 现在,您创建一个实现该接口的类:

public class MyNetworkStream : IMyNetworkStream
{
     private NetworkStream stream;

     public MyNetworkStream(NetworkStream ns)
     {
         if(ns == null) throw new ArgumentNullException("ns");
         this.stream = ns;
     }

     public bool DataAvailable
     {
         get
         {
             return this.stream.DataAvailable;
         }
     }

     public int Read([In, Out] byte[] buffer, int offset, int size)
     {
         return this.stream.Read(buffer, offset, size);
     }

}

Now you can change your method signature to use an instance of IMyNetworkStream and use Moq to create a mock of IMyNetworkStream . 现在,您可以更改方法签名以使用IMyNetworkStream的实例,并使用Moq创建IMyNetworkStream的模拟。

正如评论中所建议的那样 - 您是否可以将类型更改为Stream,以便您在测试期间可以传递MemoryStream?

将NetworkStream放在一个简单的接口(只需要你需要的调用)后面并模拟它。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM