One problem with files is that testing can be problematic, what file to use? what path to use? how to test when a file is missing? do I really need another file in my Version Control System? how to simulate 100s of files in one directory?
Sometimes it can be useful to Mock these files, for this you can create an interface IFileSystemProvider
, which gives all the classic functions of the System.IO.File
class and a FileSystemProvider
class, which link all these methods to the actual System.IO.File
class. If you do not want to instantiate a class for FileSystemProvider
you can also create a static class FileSystem
, this class instantiate a File
variable as a FileSystemProvider
by default and offers also all the methods of IFileSystemProvider
. If you do this you can use FileSystem.File
as if it was System.IO.File
, but also mock its behaviour in your unit tests!
The FileSystem class
using System.Collections.Generic;
using System.IO;
namespace FileMocking
{
public interface IFileSystemProvider
{
bool Exists(string path);
IEnumerable<string> ReadLines(string path);
}
public class FileSystemProvider : IFileSystemProvider
{
public bool Exists(string path) => File.Exists(path);
public IEnumerable<string> ReadLines(string path) => File.ReadLines(path);
}
public interface IDirectorySystemProvider
{
IEnumerable<string> EnumerateFiles(string path);
bool Exists(string path);
}
public class DirectorySystemProvider : IDirectorySystemProvider
{
public IEnumerable<string> EnumerateFiles(string path) => Directory.EnumerateFiles(path);
public bool Exists(string path) => Directory.Exists(path);
}
public static class FileSystem
{
public static IFileSystemProvider File = new FileSystemProvider();
public static IDirectorySystemProvider Directory = new DirectorySystemProvider();
}
}
Using FileSystem in your application
Since FileSystem
is a static class, its usage is very similar to System.IO.File
and System.IO.Directory
. Here are only a few functions implemented for overview but you should map all the original methods.
using FileMocking;
using System;
namespace ConsoleApp
{
class Program
{
static void Main(string[] args)
{
string filePath = @"C:\Users\username\Documents\someDoc.txt";
string directoryPath = @"C:\Users\username\Documents\";
if (FileSystem.File.Exists(filePath))
{
var lines = FileSystem.File.ReadLines(filePath);
foreach (string line in lines)
{
Console.WriteLine(line);
}
}
if (FileSystem.Directory.Exists(directoryPath))
{
var files = FileSystem.Directory.EnumerateFiles(directoryPath);
foreach (string file in files)
{
Console.WriteLine(file);
}
}
Console.ReadKey();
}
}
}
Mocking the FileSystem
In your unit test you can then easily mock FileSystem.File
and FileSystem.Directory
with other implementations of the IFileSystemProvider
and IDirectorySystemProvider
interfaces.
using FileMocking;
using System;
namespace ConsoleApp
{
public class FileSystemProviderMock : IFileSystemProvider
{
public bool Exists(string path)
{
return true;
}
public IEnumerable<string> ReadLines(string path)
{
var lines = new List<string>()
{
"line1",
"line2",
"line3",
"line4",
};
return lines.ToArray();
}
}
public class DirectorySystemProviderMock : IDirectorySystemProvider
{
public IEnumerable<string> EnumerateFiles(string path)
{
return new List<string>()
{
"file1.abc",
"file2.abc",
"file3.abc",
};
}
public bool Exists(string path)
{
return true;
}
}
class Program
{
static void Main(string[] args)
{
// Mock your File System!
FileSystem.File = new FileSystemProviderMock();
FileSystem.Directory = new DirectorySystemProviderMock();
string filePath = @"C:\Users\username\Documents\someDoc.txt";
string directoryPath = @"C:\Users\username\Documents\";
if (FileSystem.File.Exists(filePath))
{
var lines = FileSystem.File.ReadLines(filePath);
foreach (string line in lines)
{
Console.WriteLine(line);
}
}
if (FileSystem.Directory.Exists(directoryPath))
{
var files = FileSystem.Directory.EnumerateFiles(directoryPath);
foreach (string file in files)
{
Console.WriteLine(file);
}
}
Console.ReadKey();
}
}
}