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.
The best way to create a mock HTTPClient instance is by mocking HttpMessageHandler. Mocking of HttpMessageHandler will also ensure the client calling actual endpoints are faked by intercepting it. So far so good. 3. Controller with mocked IHttpClientFactory All the above 3 steps ensure our Arrange steps are properly configured within Unit Testing.
The main reason for this is that, usually, the unit tests (functional unit tests) must not use any physical components/running instances of the application. For example, the unit tests for an SDK of an API should not have any running instance of the API. Which is why, mocking the application instances becomes critical and that is the trick too!
The main thing to remember about mocks versus stubs is that mocks are just like stubs, but you assert against the mock object, whereas you do not assert against a stub. Try not to introduce dependencies on infrastructure when writing unit tests.
CppUMock is the mocking library that is included with CppUTest, the popular C/C++ unit testing framework that was used within the book Test Driven Development for Embedded C by James W. Grenning 1. This is also the framework I find myself reaching for most often, as it is full-featured, works with both C and C++, and is the most configurable.
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.
I.e., 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 (i.e. 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 - i.e. when you need some other method of communication, etc.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With