TestableIO / System.IO.Abstractions

Just like System.Web.Abstractions, but for System.IO. Yay for testable IO access!
http://www.nuget.org/packages/System.IO.Abstractions
MIT License
1.51k stars 254 forks source link

Providing mocks as and when necessary #1081

Closed sameera closed 6 months ago

sameera commented 8 months ago

Problem:

I have a parser that iterates through the file system and parses each file one by one. The functionality is built around IEnumerables and needs to ensure that the next file is read for parsing only after all downstream functions are done processing the previous one. To test for this, I want to setup a mock for File.ReadAllText and ensure that it's not being called until again the previous file is done. I could do this by setting up a mock for IFile and checking calls to ReadAllText as that's the only op I do on IFile type. I do have few other calls to other file system objects that I don't want to mock/change. If MockFileSystem.File was settable, I can assign the mock for this test only without changing anything about the other tests.

Proposed solution: Provide setters for MockFileSystem.File and MockFileSystem.Directory (for consistency and possible similar use cases).

Is your feature request related to a problem? Please describe. A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]

Alternatives: For such tests, setup a mocked IFileSystem from scratch with all objects and methods mocked. This could be tedious in many cases.

vbreuss commented 8 months ago

I am not sure, I understand your use case, @sameera.

Do you use a mocking library like Moq or NSubstitute? If so, it should be easy to not use MockFileSystem at all and simply mock the IFileSystem or IFile interface itself. Mixing the In-Memory Filesystem with a mock doesn't make sense to me, as you could then run in the situation that you write to a file via the mock and when you later read it from the In-Memory file system, you will get a FileNotFoundException.

sameera commented 8 months ago

At a very high-level, the use case was to test that certain file operations are not being done over and over. So, we'd want our unit test to test that certain file system APIs (like File.ReadAllText) are not being called more than once. There are other file system operations along this execution path, so we don't want to have to replace all of them with mock code. BTW, while I'm not yet fully familiar with this concept or library, it might be that Typemock's Isolators are a solution for this.

vbreuss commented 7 months ago

The idea behind an In-Memory file system is, that it should behave (more or less) exactly like a real file system. If we would allow replacing single calls, this would result in much more complexity, as we could no longer rely on any consistent behavior. For this I would not like to introduce any (backward-incompatible) changes in the interface.

That being said, if I understand your use case correctly, you would like some "analytical" information about which method is called how often. This could be an interesting extension to the current MockFileSystem. If you would like to work on such a feature, I would be happy to discuss more details...

sameera commented 6 months ago

Closing the loop on this: I was able to achieve this using NSubstitute:

// Setup a substitute to proxy MockFileSystem
 _fileSystem = Substitute.ForPartsOf<MockFileSystem>();
// Setup a MockFile instance that we can observe
_fileSystem.File.Returns(Substitute.ForPartsOf<MockFile>(_fileSystem));

// ....
// Other setup and testing code
// ...

// Test (in this case, verify that a file read happened only once)
_fileSystem.File.Received(1).ReadAllText(Arg.Any<string>());