This library is a feature complete testing helper for the IFileSystem abstractions for I/O-related functionality from the System.IO
namespace. It uses an in-memory file system that behaves exactly like the real file system and can be used in unit tests for dependency injection.
The testing helper also supports advanced scenarios like
FileSystemWatcher
andThe companion projects Testably.Abstractions.Compression and Testably.Abstractions.AccessControl allow working with Zip-Files and Access Control Lists respectively.
As the test suite runs both against the mocked and the real file system, the behaviour between the two is identical and it also allows simulating the file system on other operating systems (Linux, MacOS and Windows).
In addition, the following interfaces are defined:
ITimeSystem
interface abstracts away time-related functionality:
DateTime
methods give access to the current timeTask
allows replacing Task.Delay
Thread
allows replacing Thread.Sleep
Timer
is a wrapper around System.Threading.Timer
IRandomSystem
interface abstracts away functionality related to randomness:Random
methods implement a thread-safe Shared instance also under .NET Framework and Guid
methods allow creating new GUIDs.Use the interfaces and their default implementations using your prefered dependency injection method, e.g.:
private readonly IFileSystem _fileSystem;
public class MyService(IFileSystem fileSystem)
{
_fileSystem = fileSystem;
}
public void StoreData()
{
var fileContent = GetFileContent();
_fileSystem.File.WriteAllText("result.xml", fileContent);
}
private string GetFileContent()
{
// Generate the file content
}
Then you test your class with the mocked types in Testably.Abstractions.Testing
:
[Fact]
public void StoreData_ShouldWriteValidFile()
{
IFileSystem fileSystem = new MockFileSystem();
MyService sut = new MyService(fileSystem);
sut.StoreData();
var fileContent = fileSystem.File.ReadAllText("result.xml");
// Validate fileContent
}
More examples can be found in the examples section!
Install Testably.Abstractions
as nuget package in your production projects and Testably.Abstractions.Testing
as nuget package in your test projects.
dotnet add package Testably.Abstractions
dotnet add package Testably.Abstractions.Testing
Configure your dependeny injection framework, e.g. with Microsoft.Extensions.DependencyInjections
in ASP.NET core:
builder.Services
.AddSingleton<IFileSystem, RealFileSystem>()
.AddSingleton<IRandomSystem, RealRandomSystem>()
.AddSingleton<ITimeSystem, RealTimeSystem>();
You can now use the interfaces in your services!
In order to simplify testing, the Testably.Abstractions.Testing
project provides mocked instances for the abstraction interfaces, which are configured using fluent syntax:
The following two code snippets initialize the mocked fileSystem
with a structure like the following:
var fileSystem = new MockFileSystem();
fileSystem.Initialize().With(
new DirectoryDescription("foo",
new DirectoryDescription("bar"),
new FileDescription("bar.txt")),
new FileDescription("foo.txt", "some file content"));
var fileSystem = new MockFileSystem();
fileSystem.Initialize()
.WithSubdirectory("foo").Initialized(d => d
.WithSubdirectory("bar")
.WithFile("bar.txt"))
.WithFile("foo.txt").Which(f => f.HasStringContent("some file content"));
The MockFileSystem
can also simulate other operating systems than the one it is currently running on. This can be achieved, by providing the corresponding SimulationMode
in the constructor:
var linuxFileSystem = new MockFileSystem(o => o.SimulatingOperatingSystem(SimulationMode.Linux));
// The `linuxFileSystem` now behaves like a Linux file system even under Windows:
// - case-sensitive
// - slash as directory separator
var windowsFileSystem = new MockFileSystem(o => o.SimulatingOperatingSystem(SimulationMode.Windows));
// The `windowsFileSystem` now behaves like a Windows file system even under Linux or MacOS:
// - multiple drives
// - case-insensitive
// - backslash as directory separator
By running all tests against the real file system and the simulated under Linux, MacOS and Windows, the behaviour is consistent between the native and simulated mock file systems.
var fileSystem = new MockFileSystem();
fileSystem
.WithDrive("D:", d => d
.SetTotalSize(1024 * 1024))
.InitializeIn("D:")
.WithFile("foo.txt")
.WithSubdirectory("sub-dir").Initialized(s => s
.WithAFile(".json").Which(
f => f.HasStringContent("{\"count\":1}")));
Initializes the mocked file system with a second drive D:
with 1MB total available space and creates on it an empty text file foo.txt
and a directory sub-dir
which contains randomly named json file with {"count":1}
as file content.
On non-Windows systems, the main drive can still be configured, e.g.
var fileSystem = new MockFileSystem();
fileSystem.WithDrive(d => d.SetTotalSize(20));
// this will throw an IOException that there is not enough space on the disk.
fileSystem.File.WriteAllText("foo", "some text longer than 20 bytes");