Skip to content

Commit dba9d80

Browse files
committed
feat: Add support for event-driven behaviours in MockFileSystem.
Added unit tests including various event handling scenarios, such as tracking file operations, validating timestamps, monitoring access patterns, simulating controlled failures, and profiling performance metrics.
1 parent cea727b commit dba9d80

20 files changed

+3007
-173
lines changed

README.md

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,85 @@ public class SomeClassUsingFileSystemWatcher
150150
}
151151
```
152152

153+
### MockFileSystem Events
154+
155+
The `MockFileSystem` in the testing helpers now supports an event system that allows you to observe file system operations and simulate errors:
156+
157+
```csharp
158+
[Test]
159+
public void Test_SimulateDiskFullError()
160+
{
161+
var fileSystem = new MockFileSystem(new MockFileSystemOptions { EnableEvents = true });
162+
163+
using (fileSystem.Events.Subscribe(FileOperation.Write, args =>
164+
{
165+
if (args.Phase == OperationPhase.Before)
166+
{
167+
args.SetResponse(new OperationResponse
168+
{
169+
Exception = new IOException("There is not enough space on the disk.")
170+
});
171+
}
172+
}))
173+
{
174+
Assert.Throws<IOException>(() => fileSystem.File.WriteAllText(@"C:\test.txt", "content"));
175+
}
176+
}
177+
178+
[Test]
179+
public void Test_TrackFileOperations()
180+
{
181+
var fileSystem = new MockFileSystem(new MockFileSystemOptions { EnableEvents = true });
182+
var operations = new List<string>();
183+
184+
using (fileSystem.Events.Subscribe(args =>
185+
{
186+
if (args.Phase == OperationPhase.After)
187+
{
188+
operations.Add($"{args.Operation} {args.Path}");
189+
}
190+
}))
191+
{
192+
fileSystem.File.Create(@"C:\test.txt").Dispose();
193+
fileSystem.File.WriteAllText(@"C:\test.txt", "content");
194+
fileSystem.File.Delete(@"C:\test.txt");
195+
196+
Assert.That(operations, Is.EqualTo(new[] {
197+
"Create C:\\test.txt",
198+
"Write C:\\test.txt",
199+
"Delete C:\\test.txt"
200+
}));
201+
}
202+
}
203+
204+
[Test]
205+
public void Test_SimulateFileInUse()
206+
{
207+
var fileSystem = new MockFileSystem(new MockFileSystemOptions { EnableEvents = true });
208+
fileSystem.AddFile(@"C:\locked.db", "data");
209+
210+
// Simulate file locking for .db files
211+
using (fileSystem.Events.Subscribe(args =>
212+
{
213+
if (args.Phase == OperationPhase.Before &&
214+
args.Path.EndsWith(".db") &&
215+
args.Operation == FileOperation.Delete)
216+
{
217+
args.SetResponse(new OperationResponse
218+
{
219+
Exception = new IOException("The file is in use.")
220+
});
221+
}
222+
}))
223+
{
224+
var exception = Assert.Throws<IOException>(() => fileSystem.File.Delete(@"C:\locked.db"));
225+
Assert.That(exception.Message, Is.EqualTo("The file is in use."));
226+
}
227+
}
228+
```
229+
230+
The event system is opt-in and has almost zero overhead when not enabled. Enable it by setting `EnableEvents = true` in `MockFileSystemOptions`.
231+
153232
## Related projects
154233

155234
- [`System.IO.Abstractions.Extensions`](https://github.com/TestableIO/System.IO.Abstractions.Extensions)
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
namespace System.IO.Abstractions.TestingHelpers.Events;
2+
3+
/// <summary>
4+
/// Represents the type of file system operation.
5+
/// </summary>
6+
public enum FileOperation
7+
{
8+
/// <summary>
9+
/// File or directory creation operation.
10+
/// </summary>
11+
Create,
12+
13+
/// <summary>
14+
/// File open operation.
15+
/// </summary>
16+
Open,
17+
18+
/// <summary>
19+
/// File write operation.
20+
/// </summary>
21+
Write,
22+
23+
/// <summary>
24+
/// File read operation.
25+
/// </summary>
26+
Read,
27+
28+
/// <summary>
29+
/// File or directory deletion operation.
30+
/// </summary>
31+
Delete,
32+
33+
/// <summary>
34+
/// File or directory move operation.
35+
/// </summary>
36+
Move,
37+
38+
/// <summary>
39+
/// File or directory copy operation.
40+
/// </summary>
41+
Copy,
42+
43+
/// <summary>
44+
/// Set attributes operation.
45+
/// </summary>
46+
SetAttributes,
47+
48+
/// <summary>
49+
/// Set file times operation.
50+
/// </summary>
51+
SetTimes,
52+
53+
/// <summary>
54+
/// Set permissions operation.
55+
/// </summary>
56+
SetPermissions
57+
}
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
namespace System.IO.Abstractions.TestingHelpers.Events;
2+
3+
/// <summary>
4+
/// Provides data for file system operation events.
5+
/// </summary>
6+
public class FileSystemOperationEventArgs : EventArgs
7+
{
8+
private OperationResponse response;
9+
10+
/// <summary>
11+
/// Initializes a new instance of the <see cref="FileSystemOperationEventArgs"/> class.
12+
/// </summary>
13+
/// <param name="path">The path of the resource being operated on.</param>
14+
/// <param name="operation">The type of operation.</param>
15+
/// <param name="resourceType">The type of resource.</param>
16+
/// <param name="phase">The phase of the operation.</param>
17+
public FileSystemOperationEventArgs(
18+
string path,
19+
FileOperation operation,
20+
ResourceType resourceType,
21+
OperationPhase phase)
22+
{
23+
Path = path ?? throw new ArgumentNullException(nameof(path));
24+
Operation = operation;
25+
ResourceType = resourceType;
26+
Phase = phase;
27+
}
28+
29+
/// <summary>
30+
/// Gets the path of the resource being operated on.
31+
/// </summary>
32+
public string Path { get; }
33+
34+
/// <summary>
35+
/// Gets the type of operation being performed.
36+
/// </summary>
37+
public FileOperation Operation { get; }
38+
39+
/// <summary>
40+
/// Gets the type of resource being operated on.
41+
/// </summary>
42+
public ResourceType ResourceType { get; }
43+
44+
/// <summary>
45+
/// Gets the phase of the operation.
46+
/// </summary>
47+
public OperationPhase Phase { get; }
48+
49+
/// <summary>
50+
/// Sets a response for the operation. Only valid for Before phase events.
51+
/// </summary>
52+
/// <param name="response">The response to set.</param>
53+
/// <exception cref="InvalidOperationException">Thrown when called on an After phase event.</exception>
54+
public void SetResponse(OperationResponse response)
55+
{
56+
if (Phase != OperationPhase.Before)
57+
{
58+
throw new InvalidOperationException("Response can only be set for Before phase events.");
59+
}
60+
61+
this.response = response ?? throw new ArgumentNullException(nameof(response));
62+
}
63+
64+
/// <summary>
65+
/// Gets the response set for this operation, if any.
66+
/// </summary>
67+
internal OperationResponse GetResponse() => response;
68+
}

0 commit comments

Comments
 (0)