Reputation: 3958
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:
Is this the only solution to this problem?
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
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:
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
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