Tim Long
Tim Long

Reputation: 13778

What are good ways to unit test interaction with the filesystem?

I'm working on a simple project more as an exercise in TDD than anything else. The program fetches some images from a web server and saves them as files. For the record, what I am doing (my desired end result) is very similar to this perl script but in C#.

I've got to the point where I need to save the files to disk. I need to make unit tests to mandate the code. I'm not sure how to approach this. I want to be able to verify that the code created the expected files with the expected file name(s) and of course I don't want to touch the file-system at all. I'm not completely new to unit testing and TDD but for some reason I'm really not clear what to do in this situation. I'm sure the answer will be obvious once I've seen it but.... the mysterious place in my brain where code comes from is just not cooperating.

My tools of choice are MSpec and FakeItEasy, but suggestions in any frameworks would be gratefully received. What are sensible approaches to unit testing file system interactions?

Upvotes: 2

Views: 883

Answers (1)

Olivier Jacot-Descombes
Olivier Jacot-Descombes

Reputation: 112279

What would help here is Dependency Injection. Break up the monolithic download operation into smaller pieces and inject them into the downloader. Declare interfaces for these pieces:

public interface IImageFetcher
{
    IEnumerable<Image> FetchImages(string address);
}

public interface IImagePersistor
{
    void StoreImage(Image image, string path);
}

With these declarations you can write a downloader class that integrates the whole thing like this:

public class ImageDownloader
{
    private IImageFetcher _imageFetcher;
    private IImagePersistor _imagePersistor;

    // Constructor injection of components
    public ImageDownloader(IImageFetcher imageFetcher, IImagePersistor imagePersistor)
    {
        _imageFetcher = imageFetcher;
        _imagePersistor = imagePersistor;
    }

    public void Download(string source, string destination)
    {
        var images = _imageFetcher.FetchImages(source);
        int i = 1;
        foreach (Image img in images) {
            string path = Path.Combine(destination, "Image" + i.ToString("000"));
            _imagePersistor.StoreImage(img, path);
            i++;
        }
    }
}

Note that ImageDownloader does not know which implementations will be used and how they work.

Now, you can supply a dummy persistor when testing, that stores the filenames in a List<string> for instance, instead of supplying the real one that stores to the file system.


UPDATE

// For testing purposes only.
class DummyImagePersistor
{
    public readonly List<string> Filenames = new List<string>();

    public void StoreImage(Image image, string path)
    {
        Filenames.Add(path);
    }
}

Testing:

var persistor = new DummyImagePersistor();
var sut = new ImageDownloader(new ImageFetcher(), persistor);
sut.Download("http://myimages.com/images", "C:\Destination");
Assert.AreEqual(10, persistor.Filenames.Count);
...

Upvotes: 5

Related Questions