9
\$\begingroup\$

Basically I have some methods that access the file system and to avoid that in the unit test I have broken those sections out into a protected virtual method. In the Unit Test I then use Moq to setup those protected methods the way I want. I am just curious if I am going about this correctly or is there a better way? Below is an example method I have done this with however I have several places I do this with.

Method that gets unit tested:

public IEnumerable<ConfigProfile> GetSavedProfiles(string product)
    {
        if(string.IsNullOrEmpty(product))
        {
            throw new ArgumentNullException("product");
        }

        string profileDirectory = _factory.GetCustomProfileDirectory(product);
        List<ConfigProfile> profiles = Enumerable.Empty<ConfigProfile>().ToList();

        _logger.Debug("Getting Saved profiles.");

        IList<FileInfo> files = GetProfilesInDirectory(profileDirectory).ToList();

        if (!files.Any())
        {
            _logger.Debug(string.Format("No custom profiles found for {0}.", product));
            return profiles;
        }

        profiles.AddRange(files.Select(LoadProfile));

        return profiles;

    }

Then I have these two protected methods I have taken out and setup in my unit test:

protected virtual IEnumerable<FileInfo> GetProfilesInDirectory(string directory)
    {
        DirectoryInfo files = new DirectoryInfo(directory);

        return files.EnumerateFiles("*.ecu");
    }

protected virtual ConfigProfile LoadProfile(FileInfo file)
    {
        ConfigProfile profile;
        using (FileStream stream = new FileStream(file.FullName, FileMode.Open))
        {
            profile = _serializer.DeserializeFromDisk(stream);
        }
        return profile;
    }
\$\endgroup\$
  • \$\begingroup\$ Look at systemwrapper.codeplex.com for a different approach. \$\endgroup\$ – Gene S Oct 19 '12 at 17:26
  • \$\begingroup\$ Nice that does look very helpful. If you want to create a post I will mark it as my answer. \$\endgroup\$ – Tyler Reid Oct 19 '12 at 17:57
  • 1
    \$\begingroup\$ As an aside, I would suggest you avoid serializing objects, as can cause problems if you ever need to change assembly versions or even the .NET runtime you use. I've been bitten a number of times by legacy code which does this. \$\endgroup\$ – Dan Lyons Oct 19 '12 at 18:27
  • 1
    \$\begingroup\$ How would you suggest I persist those objects to disk then? \$\endgroup\$ – Tyler Reid Oct 19 '12 at 18:47
  • \$\begingroup\$ Serialization is fine in principle, but I'd avoid BinaryFormatter in favour of a well defined format based on the public properties of an object. It might be a good idea to use special DTO classes for serialization instead of the model itself. \$\endgroup\$ – CodesInChaos Jan 26 '15 at 17:12
8
\$\begingroup\$

Another approach is to use a wrapper for the DirectoryInfo, FileInfo and FileStream. Then your methods can use the wrappers and inject your mocks in the unit tests. Several people have already thought of this and have written the wrappers for you.

The one I like is SystemWrapper. Their sample page shows some good examples.

Of course, you still have the challenge of refactoring your code to inject the wrappers into your method, since creating a new instance of the wrappers within the code leaves you the same problem. The simpliest approach to injecting wrappers is to pass them in as parameters. But, since the constructor of the wrappers needs information obtained within the method this is more difficult in your case. You may consider breaking your method into several methods in a manner such as this...

NOTE: This code will not compile. I am just trying to give you an idea of how to break up the method to make it unit testable. In the end, every method cannot be unit tested. The key is to break it up into small enough methods so as much code as possible is tested.

public IDirectoryInfoWrap GetDirectoryInfoWrapper(string product)
{
    if(string.IsNullOrEmpty(product)) 
    { 
        throw new ArgumentNullException("product"); 
    } 

    string profileDirectory = _factory.GetCustomProfileDirectory(product); 
    return new DirectoryInfoWrap(profileDirectory);
}

public IList<FileInfo> GetProfileFiles(IDirectoryInfoWrap di) 
{ 
    _logger.Debug("Getting Saved profiles."); 

    // NOTE:  EnumerateFiles is not in the SystemWrapper so you will have to use an alternative
    IList<FileInfo> files = di.EnumerateFiles("*.ecu").ToList(); 
    return files;
 }

// Left for you to do...refactor in a manner so that FileSystem is using the IFileSystemWrap
public IEnumerable<ConfigProfile> GetSavedProfiles(IList<FileInfo> files)
{
    List<ConfigProfile> profiles = Enumerable.Empty<ConfigProfile>().ToList(); 
    if (!files.Any()) 
    { 
        _logger.Debug(string.Format("No custom profiles found for {0}.", product)); 
        return profiles; 
    } 

    profiles.AddRange(files.Select(LoadProfile)); 

    return profiles; 

} 
\$\endgroup\$
1
\$\begingroup\$

Actually you can pass File.Delete as parameter of the function - e.g

private void Method( Action<string> deleteFile)

And in Unit Test just do following

Method((file) => { <VALIDATION> };
\$\endgroup\$

protected by Jamal Jun 4 at 2:45

Thank you for your interest in this question. Because it has attracted low-quality or spam answers that had to be removed, posting an answer now requires 10 reputation on this site (the association bonus does not count).

Would you like to answer one of these unanswered questions instead?

Not the answer you're looking for? Browse other questions tagged or ask your own question.