How to mock FileStream with System.IO.Abstraction?

Viewed 8577

I'm trying to use the System.IO.Abstraction project along with System.IO.Abstraction.TestingHelpers to mock a FileStream.

This is the code that's using the file stream that I want to test:

private readonly IFileSystem _fileSystem;

public void ExtractImageAndSaveToDisk(IXLPicture xlPicture, string filePath)
{
    using (MemoryStream ms = new MemoryStream())
    {
        xlPicture.ImageStream.CopyTo(ms);

        using (FileStream fs = (FileStream)_fileSystem.FileStream.Create(filePath, FileMode.Create))
        {
            ms.CopyTo(fs);
            fs.Flush();
            fs.Close(); 
        }
    }
}

And this is how I've set up the testing:

[TestMethod]
public void CheckFileIsSavedToDisk()
{
    // Arrange
    var mockFileSystem = new MockFileSystem(new Dictionary<string, MockFileData>
    {
        { @"c:\Test\Images\", new MockDirectoryData() },
    });

    var xlPicture = GetXLPicture();
    var filePath = @"c:\Test\Images\myimage.jpeg";

    var sut = new CostSheetImageExtractor(mockFileSystem);

    // Act
    sut.ExtractImagesAndSaveToDisk(xlPicture, filePath);
}

Running that I get the exception:

System.InvalidCastException: Unable to cast object of type 'System.IO.Abstractions.TestingHelpers.MockFileStream' to type 'System.IO.FileStream'.

on the using (FileStream fs = ... line.

The first thought was that I need to change the FileStream fs to use an interface that both the real and mock objects share, but as far as I can see there's no IFileStream interface that FileStream and MockFileStream share, so I'm guessing I'm doing this completely wrong? Is there actually a way to test this code with System.IO.Abstraction?

This existing answer seems to suggest this should be possible, I tried doing it that way too but got the same results.

2 Answers

Not mocking the filesystem is by far the best option, as Chris F Carroll answer says, but if you want to really do that, your question almost contains the answer.

As you said, there is no such thing as an IFileStream interface, but if you look at the definition of FileStream you'll find that it inherits the abstract class Stream, and that's a common base class for all kinds of streamed data sources. The fake MockFileStream should also inherit from there.

So, try changing your code to use the abstract class instead:

using (Stream fs = (Stream)_fileSystem.FileStream.Create(filePath, FileMode.Create))
{
    ms.CopyTo(fs);
    fs.Flush();
    fs.Close(); 
}

It still retain all the base methods and your code just uses the base ones.

My first reaction is, you nearly never need to mock the filesystem. You have a Real One, fully working, on your machine, and on your build server, and even in your CI pipeline containers, and it works really really fast too. So why mock it? That appears, on the face of it, to be waste. Mocking is nearly always a compromise with a cost, so not mocking is usually better.

What you might need, if you go this route and test againt the filesystem, is something like System.IO.Directory.GetCurrentDirectory() or System.IO.Path.GetTempPath() to get a place your test suite can call its own, and reliably write, read and (optionally) delete afterwards.

Related