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.AbstractionsNote: 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.AreEqual("We can't go on together. It's not me, it's you.",ex.Message);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 Moq:
[Test]publicvoidTest1(){varwatcher=Mock.Of<IFileSystemWatcher>();varfile=Mock.Of<IFile>();Mock.Get(file).Setup(f =>f.Exists(It.IsAny<string>())).Returns(true);Mock.Get(file).Setup(f =>f.ReadAllText(It.IsAny<string>())).Throws<OutOfMemoryException>();varunitUnderTest=newSomeClassUsingFileSystemWatcher(watcher,file);Assert.Throws<OutOfMemoryException>(()=>{Mock.Get(watcher).Raise(w =>w.Created+=null,newSystem.IO.FileSystemEventArgs(System.IO.WatcherChangeTypes.Created,@"C:\Some\Directory","Some.File"));});Mock.Get(file).Verify(f =>f.Exists(It.IsAny<string>()),Times.Once);Assert.True(unitUnderTest.FileWasCreated);}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);}}}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.Testably.Abstractionsprovides alternative test helpers and additional abstractions.