简体   繁体   中英

Mock UdpClient for unit testing

I am working on a class that utilises a UdpClient, and attempting to learn/utilise a TDD approach using NUnit and Moq in the process.

A bare-bones part of my class so far is as follows:

public UdpCommsChannel(IPAddress address, int port)
{
    this._udpClient = new UdpClient();
    this._address = address;
    this._port = port;
    this._endPoint = new IPEndPoint(address, port);
}

public override void Open()
{
    if (this._disposed) throw new ObjectDisposedException(GetType().FullName);

    try
    {
        this._udpClient.Connect(this._endPoint);
    }
    catch (SocketException ex)
    {
        Debug.WriteLine(ex.Message);
    }
}

public override void Send(IPacket packet)
{
    if (this._disposed) throw new ObjectDisposedException(GetType().FullName);

    byte[] data = packet.GetBytes();
    int num = data.Length;

    try
    {
        int sent = this._udpClient.Send(data, num);
        Debug.WriteLine("sent : " + sent);
    }
    catch (SocketException ex)
    {
        Debug.WriteLine(ex.Message);
    }
}

.
.
For the Send method, I have the following unit test at the moment:

[Test]
public void DataIsSent()
{
    const int port = 9600;

    var mock = new Mock<IPacket>(MockBehavior.Strict);
    mock.Setup(p => p.GetBytes()).Returns(new byte[] { }).Verifiable();

    using (UdpCommsChannel udp = new UdpCommsChannel(IPAddress.Loopback, port))
    {
        udp.Open();
        udp.Send(mock.Object);
    }

    mock.Verify(p => p.GetBytes(), Times.Once());
}

.
.
I'm not that happy with that because it is using an actual IP address, albeit only the localhost, and the UdpClient inside the class is physically sending data to it. Therefore, this is not a true unit test as far as I understand it.

Problem is, I can't get my head around exactly what to do about it. Should I change my class and pass in a new UdpClient as a dependency perhaps? Mock the IPAddress somehow?

Struggling a bit, so I need to stop here to see if I am on the right track before carrying on. Any advice appreciated!

(Using NUnit 2.5.7, Moq 4.0 and C# WinForms.).
.

UPDATE:

OK, I have refactored my code as follows:

Created an IUdpClient interface:

public interface IUdpClient
{
    void Connect(IPEndPoint endpoint);
    int Send(byte[] data, int num);
    void Close();
}

.

Created an adapter class to wrap the UdpClient system class:

public class UdpClientAdapter : IUdpClient
{
    private UdpClient _client;

    public UdpClientAdapter()
    {
        this._client = new UdpClient();
    }

    #region IUdpClient Members

    public void Connect(IPEndPoint endpoint)
    {
        this._client.Connect(endpoint);
    }

    public int Send(byte[] data, int num)
    {
        return this._client.Send(data, num);
    }

    public void Close()
    {
        this._client.Close();
    }

    #endregion
}

.

Refactored my UdpCommsChannel clas to require an instance of an IUdpClient injected via the constructor:

public UdpCommsChannel(IUdpClient client, IPEndPoint endpoint)
{
    this._udpClient = client;
    this._endPoint = endpoint;
}

.

My unit test now looks like this:

[Test]
public void DataIsSent()
{
    var mockClient = new Mock<IUdpClient>();
    mockClient.Setup(c => c.Send(It.IsAny<byte[]>(), It.IsAny<int>())).Returns(It.IsAny<int>());

    var mockPacket = new Mock<IPacket>(MockBehavior.Strict);
    mockPacket.Setup(p => p.GetBytes()).Returns(new byte[] { }).Verifiable();

    using (UdpCommsChannel udp = new UdpCommsChannel(mockClient.Object, It.IsAny<IPEndPoint>()))
    {
        udp.Open();
        udp.Send(mockPacket.Object);
    }

    mockPacket.Verify(p => p.GetBytes(), Times.Once());
}

.

Any further comments welcome.

If you want to test only the functionality of your class, the I would go creating an interface ICommunucator, then create a class UdpCommunicator, which directly wraps UdpClient properties and methods needed, without any checks and conditions.

In your class, inject the wrapper and use is instead of tf UdpClient.

That way, in the tests you can mock ISender, and test.

Of course, you will not have tests for the wrapper itself, but if it's just a plain call forwarding, you do not need this.

Basically, you have started in the right direction, but you have added some custom logic, which can not be tested that way - so you need to split the custom logic and the comm part.

Ie, your class becomes like this:

public MyClass(ICommunicator comm)
{
     public void Method1(someparam)
     {
         //do some work with ICommunicator
     }
     ....
}

And your test will look like:

var mockComm = Mock.GetMock<ICommunicator>();
mockComm.Setup ....
var myTestObj = new MyClass(mock.Object);
MyClass.Method1(something);

mock.Verify....

So, in few Verify statements you can check if Open on the communicator is called, if the right data was passed, etc.

In general - you do not need to test system or third-party classes, only your own.

If your code uses such a classes, make them injectable. If these classes do not have virtual methods (ie you can not mock them directly), or do not implement some common interface (like SqlConnection, etc. does - it. implements IDbConnection), then you create a plain wrapper like explained above.

Besides of the testability improvements, such an approach will make it much easier to modify your code in the future - ie when you need some other method of communication, etc.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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