Mocking sophisticated third-party libraries

I have a class that uses the third-party FTP library http://ftps.codeplex.com/ , and I would like to mock it so that I can unit test just this class, not the FTP library. I did it, but it seems useless to me. In detail, the class uses these methods of the AlexPilotti.FTPS.Client.FTPSClient class:

public string Connect(string hostname, ESSLSupportMode sslSupportMode) public ulong PutFile( string localFileName, string remoteFileName, FileTransferCallback transferCallback) 

Delegating AlexPilotti.FTPS.Client.FileTransferCallback is as follows:

 public delegate void FileTransferCallback( FTPSClient sender, ETransferActions action, string localObjectName, string remoteObjectName, ulong fileTransmittedBytes, ulong? fileTransferSize, ref bool cancel); 

You can see the problem because PutFile accepts a delegate from the FTP library, and this delegate also uses two different types from the library. I decided to use generics to decouple these types.

To create a mock object, I created an interface and then a derived class, which is a wrapper for the functions of the FTP library.

 // Interface so that dependency injection can be used. public delegate void FileTransferCallback<T1, T2>(T1 sender, T2 action, string localObjectName, string remoteObjectName, ulong fileTransmittedBytes, ulong? fileTransferSize, ref bool cancel); public interface IFTPClient<T1, T2> : IDisposable { string Connect(string hostname, System.Net.NetworkCredential credential); ulong PutFile( string localFileName, string remoteFileName, FileTransferCallback<T1, T2> transferCallback); } // Derived class, the wrapper using FTPSClient = FTPS.Client.FTPSClient; using ETransferActions = FTPS.Client.ETransferActions; public class FTPClient : IFTPClient<FTPSClient, ETransferActions> { public FTPClient() { client = new AlexPilotti.FTPS.Client.FTPSClient(); } public ulong PutFile( string localFileName, string remoteFileName, FileTransferCallback<FTPSClient, ETransferActions> transferCallback) { callback = transferCallback; return client.PutFile(localFileName, remoteFileName, TransferCallback); } public void Dispose() { client.Dispose(); } public string Connect( string hostname, System.Net.NetworkCredential credential) { return client.Connect(hostname, credential, FTPS.Client.ESSLSupportMode.ClearText); } void TransferCallback( FTPS.Client.FTPSClient sender, FTPS.Client.ETransferActions action, string localObjectName, string remoteObjectName, ulong fileTransmittedBytes, ulong? fileTransferSize, ref bool cancel) { callback.Invoke(sender, action, localObjectName, remoteObjectName, fileTransmittedBytes, fileTransferSize, ref cancel); } private AlexPilotti.FTPS.Client.FTPSClient client; private FileTransferCallback<FTPSClient, ETransferActions> callback; } 

Here comes a class that uses this interface and which I unit test. I have separated it a bit, so only the Connect method is used, but I hope that it still illustrates my problem.

 public class FTPServerConnection<T1, T2> { public void Init( IFTPClient<T1, T2> client, string serverName, string userName, string passwd) { IsConnected = false; ServerName = serverName; UserName = userName; Passwd = passwd; ftpClient = client; Connect(); } public void Connect() { ftpClient.Connect( ServerName, new System.Net.NetworkCredential(UserName, Passwd)); } public string ServerName { protected set; get; } public string UserName { protected set; get; } public string Passwd { protected set; get; } public bool IsConnected { protected set; get; } private IFTPClient<T1, T2> ftpClient; } 

And finally, unit test:

 [TestMethod()] public void FTPServerConnectionConstructorTest() { var ftpClient = new Mock<IFTPClient<object, object>>(); var ftpServer = new FTPServerConnection<object, object>(); ftpServer.Init(ftpClient.Object, "1.2.3.4", "user", "passwd"); Assert.AreEqual("1.2.3.4", ftpServer.ServerName); Assert.AreEqual("user", ftpServer.UserName); Assert.AreEqual("passwd", ftpServer.Passwd); } 

What is the usual approach in situations like this? My approach seems dirty, and I wonder if there is an easier way to do this. Thanks.

+4
source share
1 answer

I think this only seems useless because you have that third-party delegate that you can handle, but it seems like a pretty standard abstraction / mocking approach to me.

My only comment would be that the FTPServerConnection structure looks a little non-standard; as it was written, you need to create an instance of one in an invalid state (that is, without client data), and then call Init() , which itself calls Connect() (which from the look of your test is never called by the client server class). It would be more normal, I would say that the Init() arguments are supplied in the FTPServerConnection constructor, and then they remove the Init() method and the client does this:

 var ftpClient = new Mock<IFTPClient<object, object>>(); using (var ftpServer = new FTPServerConnection<object, object>( ftpClient.Object, "1.2.3.4", "user", "passwd")) { ftpServer.Connect(); } 

... but then again, given how you abstracted the FTP library, I think this is a decent way :)

+1
source

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


All Articles