patterncsharpMinor
Unit tests for methods that access the file system
Viewed 0 times
thefilesystemtestsmethodsthatforaccessunit
Problem
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:
Then I have these two protected methods I have taken out and setup in my unit test:
Method that gets unit tested:
public IEnumerable GetSavedProfiles(string product)
{
if(string.IsNullOrEmpty(product))
{
throw new ArgumentNullException("product");
}
string profileDirectory = _factory.GetCustomProfileDirectory(product);
List profiles = Enumerable.Empty().ToList();
_logger.Debug("Getting Saved profiles.");
IList 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 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;
}Solution
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.
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 GetProfileFiles(IDirectoryInfoWrap di)
{
_logger.Debug("Getting Saved profiles.");
// NOTE: EnumerateFiles is not in the SystemWrapper so you will have to use an alternative
IList 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 GetSavedProfiles(IList files)
{
List profiles = Enumerable.Empty().ToList();
if (!files.Any())
{
_logger.Debug(string.Format("No custom profiles found for {0}.", product));
return profiles;
}
profiles.AddRange(files.Select(LoadProfile));
return profiles;
}Code Snippets
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;
}Context
StackExchange Code Review Q#17725, answer score: 8
Revisions (0)
No revisions yet.