4

I have method which fetches list of files from FTP server, just like below:

    public List<string> FetchFilesList()
    {
        var request = WebRequest.Create(FtpServerUri);
        request.Method = WebRequestMethods.Ftp.ListDirectory;

        request.Credentials = Credentials;

        using (var response = request.GetResponse())
        {
            var responseStream = response.GetResponseStream();

            using (var reader = new StreamReader(responseStream))
            {
                var fileNamesString = reader.ReadToEnd();
                var fileNames = fileNamesString.Split(
                    Environment.NewLine.ToCharArray(), 
                    StringSplitOptions.RemoveEmptyEntries);

                return fileNames.ToList();
            }
        }
    }

I'm trying to write UT, which would access my local directory instead of FTP.

Here is my UT:

// ftpFetcherTestWrapper is thin wrapper to expose properties to UT
// TODO: For some reason providing credentials like this doesn't work :(
// It also doesn't work for DefaultNetworkCredentials
ftpFetcherTestWrapper.CredentialsExposed = CredentialCache.DefaultCredentials;
// As we are using wrapper we substitude FtpServerUri to our fake-local folder
ftpFetcherTestWrapper.ServerUriExposed = new Uri(directoryName);

var filesList = ftpSFetcherTestWrapper.FetchFilesList();

I receive System.Net.WebException: "Access to the path 'D:\\SomePathToUnitTestsExecutionFolder\\XmlFiles' is denied."

Q: Is it possible to pass windows/network credentials to be used by WebRequest to access local folder instead of remote FTP in unit test?

[EDIT]

Note that I'm able to test fetching file contents, when providing local file path, instead of ftp one:

public XDocument FetchFile(string fileName)
{
    var client = new WebClient();
    client.Credentials = Credentials;

    var fileUri = new Uri(FtpServerUri, fileName);
    var downloadedXml = client.DownloadString(fileUri);
    return XDocument.Parse(downloadedXml);
}

Also I'm aware that what I do doesn't 100% feet definition of Unit Test, but I still think this is a good test.

Andriy Buday
  • 1,959
  • 1
  • 17
  • 40
  • There is nice tool at github: https://github.com/Buthrakaur/FtpIntegrationTesting/tree/master/FtpIntegrationTesting, but it actually executes ftpdmin.exe. Still not really what I want. – Andriy Buday Apr 10 '12 at 16:51

3 Answers3

2

Testing fetchFilesList() with it's dependencies (to the Webrequest) is called integration testing. I think you want to write a unit test, i.e. testing that the fetchFilesList() is specifically doing. You don't want to test the WebRequest in an unit test. You should assert that it does what it should. To achieve this, you need to encapsulate it, and to mock it. I would personally create a FtpWebRequestFactory that configures and returns the WebRequest.

private FtpWebRequestFactory ftpWebRequestFactory;

public FtpFetcher(FtpWebRequestFactory ftpWebRequestFactory) {
    this.ftpWebRequestFactory = ftpWebRequestFactory;
}

public List<string> FetchFilesList()
{
    var request = ftpWebRequestFactory.Create(FtpServerUri);

    using (var response = request.GetResponse())
    {
        var responseStream = response.GetResponseStream();

        using (var reader = new StreamReader(responseStream))
        {
            var fileNamesString = reader.ReadToEnd();
            var fileNames = fileNamesString.Split(
                    Environment.NewLine.ToCharArray(),
                    StringSplitOptions.RemoveEmptyEntries);

            return fileNames.ToList();
        }
    }
}

Now in your unit test, use a mock framework (like moq) to create a mock for the FtpWebRequestFactory. Configure the mock to return a mock request, and configure this mock request to return a mock response. At this point you can easily configure the response to return the correct stream.

Pierre-Henri
  • 1,495
  • 10
  • 14
  • Thank you for the response. It makes sense. Not that I did not think about adding more abstraction, but one you propose looks elegant. I'm still wondering if there is way to run code I have on folder, because I have similar method, which fetches file contents and works just fine inside of UT! So there has to be something with request.Method I guess. – Andriy Buday Apr 15 '12 at 20:06
1

what about sharing that folder and then accessing it as it would be a network path? I.e. \\my-pc-name\the-folder?

googled a bit: http://www.velocityreviews.com/forums/t78357-webrequest-and-shared-files.html

WebRequest request = WebRequest.Create("http://localhost/tempasp/temp/trace.txt" );
request.PreAuthenticate = true;
request.Credentials = new NetworkCredential("user", "pass", "domain" );

WebResponse response = request.GetResponse();
StreamReader reader = new StreamReader( response.GetResponseStream() );

string line = String.Empty;
while ( ( line = reader.ReadLine() ) != null )
{
    Response.Write( line );
    Response.Write( "<BR>" );
}
reader.Close();
response.Close();

is this gonna work for you?

avs099
  • 10,937
  • 6
  • 60
  • 110
  • This may work. I would like to avoid any manual setup needed for unit tests, like sharing some folder. I will check it later and let you know if it works for me. Still looking for less painful solution. Thanks. – Andriy Buday Apr 12 '12 at 14:55
  • you can share/unshare folder in your [TestInitialize()] and [TestCleanup()] methods using code from this SO question, for example, http://stackoverflow.com/questions/1783372/create-shared-folder-accessible-from-domain-with-c-sharp – avs099 Apr 12 '12 at 15:30
  • I was able to use code from that answer (+some msdn help) and it indeed shared folder. I also verified that access was granted to 'Everyone', but within my unit test when I try to access "//localhost/SharedFolder" I'm still getting same type of exception. Maybe it worth to mention, that accessing local files works just fine for me in similar method, that fetches contents of file. But as you can see in my question I want request.Method to be Ftp one. – Andriy Buday Apr 15 '12 at 20:13
1

You should probably add the address Scheme (i.e., "file") to the Uri in the request. Otherwise, .NET has no idea which protocol to use to access the resource. And since you are access the a local file, in most cases, you are already authorized, thus no need to set Credentials. Please try the following code:

    static void Main(string[] args)
    {
        Uri uri = new Uri(@"file:///C:\BuddyBuild\x.xml");
        FetchFilesList(uri);
        Console.ReadLine();
    }

    static List<string> FetchFilesList(Uri uri)
    {
        var request = WebRequest.Create(uri);
        request.Method = WebRequestMethods.Ftp.ListDirectory;
        //request.Credentials = CredentialCache.DefaultCredentials;

        using (var response = request.GetResponse())
        {
            var responseStream = response.GetResponseStream();
            using (var reader = new StreamReader(responseStream))
            {
                var fileNamesString = reader.ReadToEnd();
                var fileNames = fileNamesString.Split(
                    Environment.NewLine.ToCharArray(),
                    StringSplitOptions.RemoveEmptyEntries);

                return fileNames.ToList();
            }
        }
    }

Note the first line in Main: "file:///path_to_your_file".

However, as Pierre-Henri pointed, this does not look like a good design for Unit Test. Which is the "Unit" you are going to test? The FtpWebRequest part or the part that you split file content into a list? I would instead split your FetchFilesList method into 2:

    public static List<String> FetchList(Stream stream)
    {
        using (var reader = new StreamReader(stream))
        {
            var fileNamesString = reader.ReadToEnd();
            var fileNames = fileNamesString.Split(
                Environment.NewLine.ToCharArray(),
                StringSplitOptions.RemoveEmptyEntries);

            return fileNames.ToList();
        }
    }

    public static List<string> FetchFilesList2(Uri uri)
    {
        var request = WebRequest.Create(uri);
        request.Method = WebRequestMethods.Ftp.ListDirectory;

        using (var response = request.GetResponse())
        {
            return FetchList(response.GetResponseStream());
        }
    }

This way, you can do unit tests for the 2 method separately. Especailly, you can test FetchList(Stream stream) with any Stream object you like (FileStream, MemoryStream, Stream from WebResponse, ...).

EDIT: Sorry I didn't realize you are trying to list files in a (local) directory by setting the URI to a local directory. I don't think it's possible to do that with WebRequest/Response class family. With URI parameter set to a local path, the WebRequest.Create call returns a FileWebRequest object, which internally use a FileWebStream to access local file. FileWebStream inherits FileStream, and it cannot be used for directories. BTW, if you set the URI parameter to an FTP address, the WebRequest.Create call returns a FtpWebRequest object instead. This is also one of the reason why I don't think this is a good Unit Test :). Perhaps you can try to extend FileWebRequest (FtpWebRequest is sealed) to handle directory list requests.

Jiaji Wu
  • 459
  • 3
  • 10
  • Splitting makes sense. Also I'm not working with file here but with directory. Your suggestion with "file://" works fine only when I try to download file contents within similar method. And I think this is good test, even it doesn't qualify to definition of UnitTest 100%. – Andriy Buday Apr 18 '12 at 22:59
  • Sorry I didn't realize you are trying to list files in a local directory. Updated the answer at the end of the original. – Jiaji Wu Apr 19 '12 at 11:18