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.