Last time I described how I started to test PowerShell scripts written in C#. Usually, the scripts interact with the system, so in order to unit-test them, we have to mock interaction channels. A long time ago I wrote about mocking DateTime (Polish). This time I show how to mock the file system. Even though it’s not complicated, it’s essential to do this – otherwise unit testing will be impossible.
In C#, File, Directory, FileInfo, and DirectoryInfo classes exist under the System.IO namespace. We usually use them as they are, making code like this:
How to test the method in ProductionClass? It’s possible but very fragile. First, we have to ensure that the file to be read exists in the specified directory. When the system under test (SUT) finishes execution, we have to examine the file it’s left on disk.
Easy, right? Not exactly, as we do not necessarily know the test framework working directory. Almost always we can determine correct folders, but then other problems may occur. In short, we cannot be 100% sure that we fully control the test execution, so they may be flaky.
What flakiness means and how to avoid it?
Flaky tests are not reliable, as they sometimes pass and sometimes not – without any specific reason. It may be caused by different reasons – insufficient resources, wrong assumptions on the state of the system (for example – we wrongly assume date/time), data issues. It’s good to minimalize the number of factors that may affect our tests and that’s the second reason why we want to mock the file system. The first reason is that if we interact with the external system, it’s not the unit testing 🙂
After this long introduction, we can go directly to the implementation. The idea is pretty simple. Instead of using types from the System.IO namespace directly, we create the interface whose implementation will be injected into all classes that want to interact with the file system.
Let’s name the interface as IFileSystemInteractor, and the class that implements it – FileSystemInteractor. The interface will contain all methods that the consumer classes require – for example reading from a file, writing to it, checking if a directory exists, etc.
This way separates our program from touching real files directly, and for unit-testing we can inject a mock implementation of the IFileSystemInteractor. This mock will simulate interaction with the file system on our terms.
We can create a mock implementation using a mocking library, Moq for example. Or we can write the mock class in the unit-test project. Both have advantages and we can even merge these two ways to get the best results.
In the exemplary case, I used MemoryStream as an underlying channel. It’s the simplest approach because StreamWriter/StreamReader classes cannot be easily mocked. If we cannot easy mock, then we use them in a different manner. Instead of coping with files, we cope with memory, which fulfils our needs (separation between the file system and the test).
Take a look at the code above. The ProductionClass hasn’t been changed at all. The test is almost the same too. All because we used interface instead of concrete System.IO classes. I think it’s the correct approach – simple, elegant and allowing for effortless testing.
If in the future I will find this approach incorrect, I will let you know 🙂