Mock UdpClient for unit testing

I am working on a class that uses UdpClient and trying to learn / use the TDD approach using NUnit and Moq in this process.

So far, part of my bare segment looks like this:

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 submit 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 am not so happy with this because it uses the actual IP address, although it is only localhost, and the UdpClient inside the class physically sends data to it. Therefore, as I understand it, this is not true unit test.

The problem is that I cannot figure out what to do about it. Should I change my class and pass the new UdpClient as a dependency, maybe? Give up IPAddress somehow?

Fight a bit, so I need to stop here to make sure that I am on the right track before continuing. Any advice appreciated!

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

UPDATE:

OK, I updated my code as follows:

Created the IUdpClient interface:

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

.

An adapter class has been created for the shell of 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 } 

.

Implemented my UdpCommsChannel class to require an IUdpClient instance introduced through the constructor:

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

.

Now my unit test 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 are welcome.

+6
source share
1 answer

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

In your class, insert a wrapper and use instead of tf UdpClient.

Thus, in tests you can mock ISender and test.

Of course, you will not have tests for the shell itself, but if it is just call forwarding, you do not need it.

Basically, you started in the right direction, but you added some kind of user logic that cannot be verified this way - so you need to separate the user logic and the comm part.

Ie, your class will look like this:

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

And your test will look like this:

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

So, in several Verify operations, you can check whether Open is called on the communicator, whether the correct data has been transmitted, etc.

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

If your code uses such classes, make them injectable. If these classes do not have virtual methods (i.e. you cannot simulate them directly) or do not implement any common interface (for example, SqlConnection, etc., It implements IDbConnection), then you create the usual shell as described higher.

In addition to improving the testing capabilities, this approach will greatly facilitate the modification of your code in the future, that is, when you need a different communication method, etc.

+5
source

Source: https://habr.com/ru/post/893371/


All Articles