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 System.IO.AbstractionspublicclassMyComponent{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 System.IO.Abstractions.TestingHelpers[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);}}}