Alex
Alex

Reputation: 3958

Mocking StreamReader in C# for unit test

I have code of this format:

public void parseFile(string filePath)
{
    using (var reader = new StreamReader(@filePath))
    {
        //Do something
    }
}

I now want to unit test my code, but do not want my unit tests to actually access the file system, as this will make them unreliable if the actual files are not there.

In order to prevent streamreader accessing the file system I would need to mock it. My understanding is that I can only mock a class that implements an interface.

So one solution I can see is the make a wrapper class and interface for Streamreader, which I can then mock.

My question is in 2 parts:

  1. Is this the only solution to this problem?

  2. Would it be correct to add an extra layer to my project in order to facilitate a unit test? If I followed this principle globally I could be adding a lot of extra classes?

Upvotes: 9

Views: 13902

Answers (2)

Aydin
Aydin

Reputation: 15284

The issue with the example above is that the parseFile method is creating its own instance of the StreamReader, so mocking the StreamReader wouldn't actually work because:

  1. You don't have access to the object.
  2. You can only mock interfaces or members of classes that are marked as virtual.

What you can do instead is create an interface, let's call it IFileManager for arguments sake, with a method called StreamReader.

public interface IFileManager
{
     StreamReader StreamReader(string path);
}

Then in your other class (let's call it Foo) that contains the ParseFile method you posted above:

public class Foo 
{
    IFileManager fileManager;

    public Foo(IFileManager fileManager)
    {
        this.fileManager = fileManager;
    }

    public void parseFile(string filePath)
    {
        using (var reader = fileManager.StreamReader(filePath))
        {
            //Do something
        }
    }
}

Now you can mock the IFileManager interface and its StreamReader method, you can inject this mocked instance into the Foo class making it available to the ParseFile method to use.

Now your code will depend on an abstraction as opposed to the concrete implementation, we've inverted the dependency which allows us to mock the dependencies and isolate the code we want to actually test.


A crude demonstration of creating the mock object

public static void Main()
{
    Mock<IFileManager> mockFileManager = new Mock<IFileManager>();
    string fakeFileContents = "Hello world";
    byte[] fakeFileBytes = Encoding.UTF8.GetBytes(fakeFileContents);

    MemoryStream fakeMemoryStream = new MemoryStream(fakeFileBytes);

    mockFileManager.Setup(fileManager => fileManager.StreamReader(It.IsAny<string>()))
                   .Returns(() => new StreamReader(fakeMemoryStream));

    Foo foo = new Foo(mockFileManager.Object);

    string result = foo.ParseFile("test.txt");

    Console.WriteLine(result);
}

public interface IFileManager
{
    StreamReader StreamReader(string path);
}

public class Foo
{
    IFileManager fileManager;

    public Foo(IFileManager fileManager)
    {
        this.fileManager = fileManager;
    }

    public string ParseFile(string filePath)
    {
        using (var reader = fileManager.StreamReader(filePath))
        {
            return reader.ReadToEnd();
        }
    }
}

Upvotes: 19

auburg
auburg

Reputation: 1475

Have a look at System.IO.Abstractions - this pretty much allows you to mock the System.IO .net namespace for your tests

Upvotes: 3

Related Questions