Skip to content

Commit 7336a7a

Browse files
authored
fix: DirectoryMock.EnumerateDirectories does not throw exceptions until enumerated (#857)
This PR fixes a bug where `DirectoryMock.EnumerateDirectories` (and related enumeration methods) did not match the behavior of the real `Directory.EnumerateDirectories` implementation. The real file system throws IO exceptions immediately when the enumeration method is called on a non-existent directory, but the mock implementation only threw exceptions during iteration. ### Key changes: - Split `InMemoryStorage.EnumerateLocations` into validation and implementation phases to throw exceptions immediately - Added new test cases to verify immediate exception throwing behavior for all enumeration methods - Updated statistics tests to create directories before enumeration to avoid exception scenarios
1 parent 46eb8ff commit 7336a7a

File tree

6 files changed

+91
-0
lines changed

6 files changed

+91
-0
lines changed

Source/Testably.Abstractions.Testing/Storage/InMemoryStorage.cs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,22 @@ public IEnumerable<IStorageLocation> EnumerateLocations(
199199
throw ExceptionFactory.DirectoryNotFound(location.FullPath);
200200
}
201201

202+
return EnumerateLocationsImpl(location, type, requestParentAccess, searchPattern,
203+
enumerationOptions, parentContainer);
204+
}
205+
206+
/// <summary>
207+
/// Internal implementation of location enumeration that uses yield return.
208+
/// This method contains the actual enumeration logic and is only called after validation passes.
209+
/// </summary>
210+
private IEnumerable<IStorageLocation> EnumerateLocationsImpl(
211+
IStorageLocation location,
212+
FileSystemTypes type,
213+
bool requestParentAccess,
214+
string searchPattern,
215+
EnumerationOptions? enumerationOptions,
216+
IStorageContainer parentContainer)
217+
{
202218
IDisposable parentAccess = new NoOpDisposable();
203219
if (requestParentAccess)
204220
{

Tests/Testably.Abstractions.Testing.Tests/Statistics/FileSystem/DirectoryInfoStatisticsTests.cs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ await That(sut.Statistics.DirectoryInfo["foo"])
8080
public async Task Method_EnumerateDirectories_ShouldRegisterCall()
8181
{
8282
MockFileSystem sut = new();
83+
sut.Initialize().WithSubdirectory("foo");
8384

8485
sut.DirectoryInfo.New("foo").EnumerateDirectories();
8586

@@ -93,6 +94,7 @@ await That(sut.Statistics.DirectoryInfo["foo"])
9394
public async Task Method_EnumerateDirectories_String_EnumerationOptions_ShouldRegisterCall()
9495
{
9596
MockFileSystem sut = new();
97+
sut.Initialize().WithSubdirectory("foo");
9698
string searchPattern = "foo";
9799
EnumerationOptions enumerationOptions = new();
98100

@@ -109,6 +111,7 @@ await That(sut.Statistics.DirectoryInfo["foo"])
109111
public async Task Method_EnumerateDirectories_String_SearchOption_ShouldRegisterCall()
110112
{
111113
MockFileSystem sut = new();
114+
sut.Initialize().WithSubdirectory("foo");
112115
string searchPattern = "foo";
113116
SearchOption searchOption = SearchOption.AllDirectories;
114117

@@ -124,6 +127,7 @@ await That(sut.Statistics.DirectoryInfo["foo"])
124127
public async Task Method_EnumerateDirectories_String_ShouldRegisterCall()
125128
{
126129
MockFileSystem sut = new();
130+
sut.Initialize().WithSubdirectory("foo");
127131
string searchPattern = "foo";
128132

129133
sut.DirectoryInfo.New("foo").EnumerateDirectories(searchPattern);
@@ -138,6 +142,7 @@ await That(sut.Statistics.DirectoryInfo["foo"])
138142
public async Task Method_EnumerateFiles_ShouldRegisterCall()
139143
{
140144
MockFileSystem sut = new();
145+
sut.Initialize().WithSubdirectory("foo");
141146

142147
sut.DirectoryInfo.New("foo").EnumerateFiles();
143148

@@ -151,6 +156,7 @@ await That(sut.Statistics.DirectoryInfo["foo"])
151156
public async Task Method_EnumerateFiles_String_EnumerationOptions_ShouldRegisterCall()
152157
{
153158
MockFileSystem sut = new();
159+
sut.Initialize().WithSubdirectory("foo");
154160
string searchPattern = "foo";
155161
EnumerationOptions enumerationOptions = new();
156162

@@ -167,6 +173,7 @@ await That(sut.Statistics.DirectoryInfo["foo"])
167173
public async Task Method_EnumerateFiles_String_SearchOption_ShouldRegisterCall()
168174
{
169175
MockFileSystem sut = new();
176+
sut.Initialize().WithSubdirectory("foo");
170177
string searchPattern = "foo";
171178
SearchOption searchOption = SearchOption.AllDirectories;
172179

@@ -182,6 +189,7 @@ await That(sut.Statistics.DirectoryInfo["foo"])
182189
public async Task Method_EnumerateFiles_String_ShouldRegisterCall()
183190
{
184191
MockFileSystem sut = new();
192+
sut.Initialize().WithSubdirectory("foo");
185193
string searchPattern = "foo";
186194

187195
sut.DirectoryInfo.New("foo").EnumerateFiles(searchPattern);
@@ -196,6 +204,7 @@ await That(sut.Statistics.DirectoryInfo["foo"])
196204
public async Task Method_EnumerateFileSystemInfos_ShouldRegisterCall()
197205
{
198206
MockFileSystem sut = new();
207+
sut.Initialize().WithSubdirectory("foo");
199208

200209
sut.DirectoryInfo.New("foo").EnumerateFileSystemInfos();
201210

@@ -209,6 +218,7 @@ await That(sut.Statistics.DirectoryInfo["foo"])
209218
public async Task Method_EnumerateFileSystemInfos_String_EnumerationOptions_ShouldRegisterCall()
210219
{
211220
MockFileSystem sut = new();
221+
sut.Initialize().WithSubdirectory("foo");
212222
string searchPattern = "foo";
213223
EnumerationOptions enumerationOptions = new();
214224

@@ -225,6 +235,7 @@ await That(sut.Statistics.DirectoryInfo["foo"])
225235
public async Task Method_EnumerateFileSystemInfos_String_SearchOption_ShouldRegisterCall()
226236
{
227237
MockFileSystem sut = new();
238+
sut.Initialize().WithSubdirectory("foo");
228239
string searchPattern = "foo";
229240
SearchOption searchOption = SearchOption.AllDirectories;
230241

@@ -240,6 +251,7 @@ await That(sut.Statistics.DirectoryInfo["foo"])
240251
public async Task Method_EnumerateFileSystemInfos_String_ShouldRegisterCall()
241252
{
242253
MockFileSystem sut = new();
254+
sut.Initialize().WithSubdirectory("foo");
243255
string searchPattern = "foo";
244256

245257
sut.DirectoryInfo.New("foo").EnumerateFileSystemInfos(searchPattern);

Tests/Testably.Abstractions.Testing.Tests/Statistics/FileSystem/DirectoryStatisticsTests.cs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,7 @@ await That(sut.Statistics.Directory).OnlyContainsMethodCall(nameof(IDirectory.De
103103
public async Task Method_EnumerateDirectories_String_ShouldRegisterCall()
104104
{
105105
MockFileSystem sut = new();
106+
sut.Initialize().WithSubdirectory("foo");
106107
string path = "foo";
107108

108109
sut.Directory.EnumerateDirectories(path);
@@ -118,6 +119,7 @@ await That(sut.Statistics.Directory).OnlyContainsMethodCall(
118119
public async Task Method_EnumerateDirectories_String_String_EnumerationOptions_ShouldRegisterCall()
119120
{
120121
MockFileSystem sut = new();
122+
sut.Initialize().WithSubdirectory("foo");
121123
string path = "foo";
122124
string searchPattern = "foo";
123125
EnumerationOptions enumerationOptions = new();
@@ -135,6 +137,7 @@ await That(sut.Statistics.Directory).OnlyContainsMethodCall(
135137
public async Task Method_EnumerateDirectories_String_String_SearchOption_ShouldRegisterCall()
136138
{
137139
MockFileSystem sut = new();
140+
sut.Initialize().WithSubdirectory("foo");
138141
string path = "foo";
139142
string searchPattern = "foo";
140143
SearchOption searchOption = SearchOption.AllDirectories;
@@ -151,6 +154,7 @@ await That(sut.Statistics.Directory).OnlyContainsMethodCall(
151154
public async Task Method_EnumerateDirectories_String_String_ShouldRegisterCall()
152155
{
153156
MockFileSystem sut = new();
157+
sut.Initialize().WithSubdirectory("foo");
154158
string path = "foo";
155159
string searchPattern = "foo";
156160

@@ -166,6 +170,7 @@ await That(sut.Statistics.Directory).OnlyContainsMethodCall(
166170
public async Task Method_EnumerateFiles_String_ShouldRegisterCall()
167171
{
168172
MockFileSystem sut = new();
173+
sut.Initialize().WithSubdirectory("foo");
169174
string path = "foo";
170175

171176
sut.Directory.EnumerateFiles(path);
@@ -181,6 +186,7 @@ await That(sut.Statistics.Directory).OnlyContainsMethodCall(
181186
public async Task Method_EnumerateFiles_String_String_EnumerationOptions_ShouldRegisterCall()
182187
{
183188
MockFileSystem sut = new();
189+
sut.Initialize().WithSubdirectory("foo");
184190
string path = "foo";
185191
string searchPattern = "foo";
186192
EnumerationOptions enumerationOptions = new();
@@ -197,6 +203,7 @@ await That(sut.Statistics.Directory).OnlyContainsMethodCall(nameof(IDirectory.En
197203
public async Task Method_EnumerateFiles_String_String_SearchOption_ShouldRegisterCall()
198204
{
199205
MockFileSystem sut = new();
206+
sut.Initialize().WithSubdirectory("foo");
200207
string path = "foo";
201208
string searchPattern = "foo";
202209
SearchOption searchOption = SearchOption.AllDirectories;
@@ -213,6 +220,7 @@ await That(sut.Statistics.Directory).OnlyContainsMethodCall(
213220
public async Task Method_EnumerateFiles_String_String_ShouldRegisterCall()
214221
{
215222
MockFileSystem sut = new();
223+
sut.Initialize().WithSubdirectory("foo");
216224
string path = "foo";
217225
string searchPattern = "foo";
218226

@@ -228,6 +236,7 @@ await That(sut.Statistics.Directory).OnlyContainsMethodCall(
228236
public async Task Method_EnumerateFileSystemEntries_String_ShouldRegisterCall()
229237
{
230238
MockFileSystem sut = new();
239+
sut.Initialize().WithSubdirectory("foo");
231240
string path = "foo";
232241

233242
sut.Directory.EnumerateFileSystemEntries(path);
@@ -243,6 +252,7 @@ await That(sut.Statistics.Directory).OnlyContainsMethodCall(
243252
public async Task Method_EnumerateFileSystemEntries_String_String_EnumerationOptions_ShouldRegisterCall()
244253
{
245254
MockFileSystem sut = new();
255+
sut.Initialize().WithSubdirectory("foo");
246256
string path = "foo";
247257
string searchPattern = "foo";
248258
EnumerationOptions enumerationOptions = new();
@@ -261,6 +271,7 @@ public async Task
261271
Method_EnumerateFileSystemEntries_String_String_SearchOption_ShouldRegisterCall()
262272
{
263273
MockFileSystem sut = new();
274+
sut.Initialize().WithSubdirectory("foo");
264275
string path = "foo";
265276
string searchPattern = "foo";
266277
SearchOption searchOption = SearchOption.AllDirectories;
@@ -277,6 +288,7 @@ await That(sut.Statistics.Directory).OnlyContainsMethodCall(
277288
public async Task Method_EnumerateFileSystemEntries_String_String_ShouldRegisterCall()
278289
{
279290
MockFileSystem sut = new();
291+
sut.Initialize().WithSubdirectory("foo");
280292
string path = "foo";
281293
string searchPattern = "foo";
282294

Tests/Testably.Abstractions.Tests/FileSystem/Directory/EnumerateDirectoriesTests.cs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,23 @@ await That(Act).Throws<DirectoryNotFoundException>()
4141
await That(FileSystem.Directory.Exists(path)).IsFalse();
4242
}
4343

44+
[Theory]
45+
[AutoData]
46+
public async Task
47+
EnumerateDirectories_MissingDirectory_ShouldThrowDirectoryNotFoundExceptionImmediately(
48+
string path)
49+
{
50+
string expectedPath = FileSystem.Path.Combine(BasePath, path);
51+
52+
void Act() =>
53+
_ = FileSystem.Directory.EnumerateDirectories(path);
54+
55+
await That(Act).Throws<DirectoryNotFoundException>()
56+
.WithMessageContaining($"'{expectedPath}'").And
57+
.WithHResult(-2147024893);
58+
await That(FileSystem.Directory.Exists(path)).IsFalse();
59+
}
60+
4461
[Fact]
4562
public async Task EnumerateDirectories_RelativePath_ShouldNotIncludeTrailingSlash()
4663
{

Tests/Testably.Abstractions.Tests/FileSystem/Directory/EnumerateFileSystemInfosTests.cs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,23 @@ await That(Act).Throws<DirectoryNotFoundException>()
2828
await That(FileSystem.Directory.Exists(path)).IsFalse();
2929
}
3030

31+
[Theory]
32+
[AutoData]
33+
public async Task
34+
EnumerateFileSystemEntries_MissingDirectory_ShouldThrowDirectoryNotFoundExceptionImmediately(
35+
string path)
36+
{
37+
string expectedPath = FileSystem.Path.Combine(BasePath, path);
38+
39+
void Act() =>
40+
_ = FileSystem.Directory.EnumerateFileSystemEntries(path);
41+
42+
await That(Act).Throws<DirectoryNotFoundException>()
43+
.WithMessageContaining($"'{expectedPath}'").And
44+
.WithHResult(-2147024893);
45+
await That(FileSystem.Directory.Exists(path)).IsFalse();
46+
}
47+
3148
[Theory]
3249
[AutoData]
3350
public async Task

Tests/Testably.Abstractions.Tests/FileSystem/Directory/EnumerateFilesTests.cs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,23 @@ await That(Act).Throws<DirectoryNotFoundException>()
2525
await That(FileSystem.Directory.Exists(path)).IsFalse();
2626
}
2727

28+
[Theory]
29+
[AutoData]
30+
public async Task
31+
EnumerateFiles_MissingDirectory_ShouldThrowDirectoryNotFoundExceptionImmediately(
32+
string path)
33+
{
34+
string expectedPath = FileSystem.Path.Combine(BasePath, path);
35+
36+
void Act() =>
37+
_ = FileSystem.Directory.EnumerateFiles(path);
38+
39+
await That(Act).Throws<DirectoryNotFoundException>()
40+
.WithMessageContaining($"'{expectedPath}'").And
41+
.WithHResult(-2147024893);
42+
await That(FileSystem.Directory.Exists(path)).IsFalse();
43+
}
44+
2845
[Theory]
2946
[AutoData]
3047
public async Task

0 commit comments

Comments
 (0)