At the core of the library is IFileSystem and FileSystem. Instead of calling methods like File.ReadAllText directly, use IFileSystem.File.ReadAllText. We have exactly the same API, except that ours is injectable and testable.
dotnet add package TestableIO.System.IO.Abstractions.WrappersNote: This NuGet package is also published as System.IO.Abstractions but we suggest to use the prefix to make clear that this is not an official .NET package.
publicclassMyComponent{readonlyIFileSystemfileSystem;// <summary>Create MyComponent with the given fileSystem implementation</summary>publicMyComponent(IFileSystemfileSystem){this.fileSystem=fileSystem;}/// <summary>Create MyComponent</summary>publicMyComponent():this(fileSystem:newFileSystem()//use default implementation which calls System.IO){}publicvoidValidate(){foreach(vartextFileinfileSystem.Directory.GetFiles(@"c:\","*.txt",SearchOption.TopDirectoryOnly)){vartext=fileSystem.File.ReadAllText(textFile);if(text!="Testing is awesome.")thrownewNotSupportedException("We can't go on together. It's not me, it's you.");}}}The library also ships with a series of test helpers to save you from having to mock out every call, for basic scenarios. They are not a complete copy of a real-life file system, but they'll get you most of the way there.
dotnet add package TestableIO.System.IO.Abstractions.TestingHelpersNote: This NuGet package is also published as System.IO.Abstractions.TestingHelpers but we suggest to use the prefix to make clear that this is not an official .NET package.
[Test]publicvoidMyComponent_Validate_ShouldThrowNotSupportedExceptionIfTestingIsNotAwesome(){// ArrangevarfileSystem=newMockFileSystem(newDictionary<string,MockFileData>{{@"c:\myfile.txt",newMockFileData("Testing is meh.")},{@"c:\demo\jQuery.js",newMockFileData("some js")},{@"c:\demo\image.gif",newMockFileData(newbyte[]{0x12,0x34,0x56,0xd2})}});varcomponent=newMyComponent(fileSystem);try{// Actcomponent.Validate();}catch(NotSupportedExceptionex){// AssertAssert.That(ex.Message,Is.EqualTo("We can't go on together. It's not me, it's you."));return;}Assert.Fail("The expected exception was not thrown.");}We even support casting from the .NET Framework's untestable types to our testable wrappers:
FileInfoSomeApiMethodThatReturnsFileInfo(){returnnewFileInfo("a");}voidMyFancyMethod(){vartestableFileInfo=(FileInfoBase)SomeApiMethodThatReturnsFileInfo();
...}Since version 4.0 the top-level APIs expose interfaces instead of abstract base classes (these still exist, though), allowing you to completely mock the file system. Here's a small example, using Mockolate:
[Test]publicvoidTest1(){varwatcher=Mock.Create<IFileSystemWatcher>();varfile=Mock.Create<IFile>();file.SetupMock.Method.Exists(It.IsAny<string>()).Returns(true);file.SetupMock.Method.ReadAllText(It.IsAny<string>()).Throws<OutOfMemoryException>();varunitUnderTest=newSomeClassUsingFileSystemWatcher(watcher,file);Assert.Throws<OutOfMemoryException>(()=>{watcher.RaiseOnMock.Created(null,newSystem.IO.FileSystemEventArgs(System.IO.WatcherChangeTypes.Created,@"C:\Some\Directory","Some.File"));});file.VerifyMock.Invoked.Exists(It.IsAny<string>()).Once();Assert.That(unitUnderTest.FileWasCreated,Is.True);}publicclassSomeClassUsingFileSystemWatcher{privatereadonlyIFileSystemWatcher_watcher;privatereadonlyIFile_file;publicboolFileWasCreated{get;privateset;}publicSomeClassUsingFileSystemWatcher(IFileSystemWatcherwatcher,IFilefile){this._file=file;this._watcher=watcher;this._watcher.Created+=Watcher_Created;}privatevoidWatcher_Created(objectsender,System.IO.FileSystemEventArgse){FileWasCreated=true;if(_file.Exists(e.FullPath)){vartext=_file.ReadAllText(e.FullPath);}}}Testably.Abstractions is a complementary project that uses the same interfaces as TestableIO. This means no changes to your production code are necessary when switching between the testing libraries.
Both projects share the same maintainer, but active development and new features are primarily focused on the Testably.Abstractions project. TestableIO.System.IO.Abstractions continues to be maintained for stability and compatibility, but significant new functionality is unlikely to be added.
Use TestableIO.System.IO.Abstractions if you need:
- Basic file system mocking capabilities
- Direct manipulation of stored file entities (MockFileData, MockDirectoryData)
- Established codebase with existing TestableIO integration
Use Testably.Abstractions if you need:
- Advanced testing scenarios (FileSystemWatcher, SafeFileHandles, multiple drives)
- Additional abstractions (ITimeSystem, IRandomSystem)
- Cross-platform file system simulation (Linux, MacOS, Windows)
- More extensive and consistent behavior validation
- Active development and new features
Switching from TestableIO to Testably only requires changes in your test projects:
Replace the NuGet package reference in your test projects:
<!-- Remove --> <PackageReferenceInclude="TestableIO.System.IO.Abstractions.TestingHelpers" /> <!-- Add --> <PackageReferenceInclude="Testably.Abstractions.Testing" />
Update your test code to use the new
MockFileSystem:// Before (TestableIO)varfileSystem=newMockFileSystem();fileSystem.AddDirectory("some-directory");fileSystem.AddFile("some-file.txt",newMockFileData("content"));// After (Testably)varfileSystem=newMockFileSystem();fileSystem.Directory.CreateDirectory("some-directory");fileSystem.File.WriteAllText("some-file.txt","content");// or using fluent initialization:fileSystem.Initialize().WithSubdirectory("some-directory").WithFile("some-file.txt").Which(f =>f.HasStringContent("content"));
Your production code using IFileSystem remains unchanged.
System.IO.Abstractions.Extensionsprovides convenience functionality on top of the core abstractions.System.IO.Abstractions.Analyzersprovides Roslyn analyzers to help use abstractions over static methods.