diff --git a/src/System.IO.Abstractions.TestingHelpers/MockDirectoryEvent.cs b/src/System.IO.Abstractions.TestingHelpers/MockDirectoryEvent.cs
new file mode 100644
index 000000000..939f512d2
--- /dev/null
+++ b/src/System.IO.Abstractions.TestingHelpers/MockDirectoryEvent.cs
@@ -0,0 +1,39 @@
+namespace System.IO.Abstractions.TestingHelpers
+{
+ ///
+ /// Notifies about a pending directory event.
+ ///
+ public class MockDirectoryEvent
+ {
+ ///
+ /// The path of the directory.
+ ///
+ public string Path { get; }
+
+ ///
+ /// The type of the directory event.
+ ///
+ public DirectoryEventType EventType { get; }
+
+ internal MockDirectoryEvent(string path, DirectoryEventType eventType)
+ {
+ Path = path;
+ EventType = eventType;
+ }
+
+ ///
+ /// The type of the directory event.
+ ///
+ public enum DirectoryEventType
+ {
+ ///
+ /// The directory is created.
+ ///
+ Created,
+ ///
+ /// The directory is deleted.
+ ///
+ Deleted
+ }
+ }
+}
diff --git a/src/System.IO.Abstractions.TestingHelpers/MockFileEvent.cs b/src/System.IO.Abstractions.TestingHelpers/MockFileEvent.cs
new file mode 100644
index 000000000..4a465bba1
--- /dev/null
+++ b/src/System.IO.Abstractions.TestingHelpers/MockFileEvent.cs
@@ -0,0 +1,43 @@
+namespace System.IO.Abstractions.TestingHelpers
+{
+ ///
+ /// Notifies about a pending file event.
+ ///
+ public class MockFileEvent
+ {
+ ///
+ /// The path of the file.
+ ///
+ public string Path { get; }
+
+ ///
+ /// The type of the file event.
+ ///
+ public FileEventType EventType { get; }
+
+ internal MockFileEvent(string path, FileEventType changeType)
+ {
+ Path = path;
+ EventType = changeType;
+ }
+
+ ///
+ /// The type of the file event.
+ ///
+ public enum FileEventType
+ {
+ ///
+ /// The file is created.
+ ///
+ Created,
+ ///
+ /// The file is updated.
+ ///
+ Updated,
+ ///
+ /// The file is deleted.
+ ///
+ Deleted
+ }
+ }
+}
diff --git a/src/System.IO.Abstractions.TestingHelpers/MockFileSystem.cs b/src/System.IO.Abstractions.TestingHelpers/MockFileSystem.cs
index fa3203ac9..573ba6e2c 100644
--- a/src/System.IO.Abstractions.TestingHelpers/MockFileSystem.cs
+++ b/src/System.IO.Abstractions.TestingHelpers/MockFileSystem.cs
@@ -16,6 +16,9 @@ public class MockFileSystem : FileSystemBase, IMockFileDataAccessor
private readonly IDictionary files;
private readonly PathVerifier pathVerifier;
+ private Action onFileEvent;
+ private Action onDirectoryEvent;
+
///
public MockFileSystem() : this(null) { }
@@ -88,6 +91,24 @@ public MockFileSystem(IDictionary files, string currentDir
///
public PathVerifier PathVerifier => pathVerifier;
+ ///
+ /// Registers a callback to be executed when a file event is triggered.
+ ///
+ public MockFileSystem OnFileEvent(Action callback)
+ {
+ onFileEvent = callback;
+ return this;
+ }
+
+ ///
+ /// Registers a callback to be executed when a directory event is triggered.
+ ///
+ public MockFileSystem OnDirectoryEvent(Action callback)
+ {
+ onDirectoryEvent = callback;
+ return this;
+ }
+
private string FixPath(string path, bool checkCaps = false)
{
if (path == null)
@@ -164,6 +185,15 @@ public void AddFile(string path, MockFileData mockFile)
AddDirectory(directoryPath);
}
+ var existingFile = GetFileWithoutFixingPath(fixedPath);
+ if (existingFile == null)
+ {
+ onFileEvent?.Invoke(new MockFileEvent(fixedPath, MockFileEvent.FileEventType.Created));
+ }
+ else
+ {
+ onFileEvent?.Invoke(new MockFileEvent(fixedPath, MockFileEvent.FileEventType.Updated));
+ }
SetEntry(fixedPath, mockFile ?? new MockFileData(string.Empty));
}
}
@@ -211,6 +241,8 @@ public void AddDirectory(string path)
}
var s = StringOperations.EndsWith(fixedPath, separator) ? fixedPath : fixedPath + separator;
+
+ onDirectoryEvent?.Invoke(new MockDirectoryEvent(s.TrimSlashes(), MockDirectoryEvent.DirectoryEventType.Created));
SetEntry(s, new MockDirectoryData());
}
}
@@ -269,6 +301,16 @@ public void MoveDirectory(string sourcePath, string destPath)
var newPath = Path.Combine(destPath, path.Substring(sourcePath.Length).TrimStart(Path.DirectorySeparatorChar));
var entry = files[path];
entry.Path = newPath;
+ if (entry.Data is MockDirectoryData)
+ {
+ onDirectoryEvent?.Invoke(new MockDirectoryEvent(path, MockDirectoryEvent.DirectoryEventType.Deleted));
+ onDirectoryEvent?.Invoke(new MockDirectoryEvent(newPath, MockDirectoryEvent.DirectoryEventType.Created));
+ }
+ else
+ {
+ onFileEvent?.Invoke(new MockFileEvent(path, MockFileEvent.FileEventType.Deleted));
+ onFileEvent?.Invoke(new MockFileEvent(newPath, MockFileEvent.FileEventType.Created));
+ }
files[newPath] = entry;
files.Remove(path);
}
@@ -297,7 +339,7 @@ bool PathStartsWith(string path, string[] minMatch)
///
public void RemoveFile(string path)
{
- path = FixPath(path);
+ path = FixPath(path).TrimSlashes();
lock (files)
{
@@ -306,6 +348,16 @@ public void RemoveFile(string path)
throw CommonExceptions.AccessDenied(path);
}
+ var file = GetFileWithoutFixingPath(path);
+ if (file is MockDirectoryData)
+ {
+ onDirectoryEvent?.Invoke(new MockDirectoryEvent(path, MockDirectoryEvent.DirectoryEventType.Deleted));
+ }
+ else
+ {
+ onFileEvent?.Invoke(new MockFileEvent(path, MockFileEvent.FileEventType.Deleted));
+ }
+
files.Remove(path);
}
}
diff --git a/tests/System.IO.Abstractions.TestingHelpers.Tests/MockFileSystemEventTests.cs b/tests/System.IO.Abstractions.TestingHelpers.Tests/MockFileSystemEventTests.cs
new file mode 100644
index 000000000..b12015414
--- /dev/null
+++ b/tests/System.IO.Abstractions.TestingHelpers.Tests/MockFileSystemEventTests.cs
@@ -0,0 +1,207 @@
+using System.Collections.Generic;
+using NUnit.Framework;
+
+namespace System.IO.Abstractions.TestingHelpers.Tests
+{
+ [TestFixture]
+ public class MockFileSystemEventTests
+ {
+ [Test]
+ public void OnFileChanging_ThrowExceptionInCallback_ShouldThrowExceptionAndNotCreateFile()
+ {
+ var basePath = Path.GetFullPath("/foo/bar");
+ var fileName = "foo.txt";
+ var exception = new Exception("the file should not be created");
+ var expectedPath = Path.Combine(basePath, fileName);
+ var fs = new MockFileSystem(null, basePath)
+ .OnFileEvent(_ => throw exception);
+
+ var receivedException = Assert.Throws(() => fs.File.WriteAllText(fileName, "some content"));
+ var result = fs.File.Exists(expectedPath);
+
+ Assert.That(receivedException, Is.EqualTo(exception));
+ Assert.That(result, Is.False);
+ }
+
+ [Test]
+ public void OnFileChanging_WithFileEvent_ShouldCallOnFileChangingWithFullFilePath()
+ {
+ var basePath = Path.GetFullPath("/foo/bar");
+ var fileName = "foo.txt";
+ var expectedPath = Path.Combine(basePath, fileName);
+ var calledPath = string.Empty;
+ var fs = new MockFileSystem(null, basePath)
+ .OnFileEvent(f => calledPath = f.Path);
+
+ fs.File.WriteAllText(fileName, "some content");
+
+ Assert.That(calledPath, Is.EqualTo(expectedPath));
+ }
+
+ [Test]
+ public void OnFileChanging_WithDirectoryEvent_ShouldNotBeCalled()
+ {
+ var basePath = Path.GetFullPath("/foo/bar");
+ var directoryName = "test-directory";
+ bool isCalled = false;
+ var fs = new MockFileSystem(new Dictionary
+ {
+ { directoryName, new MockFileData("some content") }
+ }, basePath)
+ .OnFileEvent(f => isCalled = true);
+
+ _ = fs.Directory.CreateDirectory(directoryName);
+
+ Assert.That(isCalled, Is.False);
+ }
+
+ [Test]
+ public void OnDirectoryChanging_ThrowExceptionInCallback_ShouldThrowExceptionAndNotCreateDirectory()
+ {
+ var basePath = Path.GetFullPath("/foo/bar");
+ var directoryName = "test-directory";
+ var exception = new Exception("the directory should not be created");
+ var expectedPath = Path.Combine(basePath, directoryName);
+ var fs = new MockFileSystem(null, basePath)
+ .OnDirectoryEvent(_ => throw exception);
+
+ var receivedException = Assert.Throws(() => fs.Directory.CreateDirectory(directoryName));
+ var result = fs.Directory.Exists(expectedPath);
+
+ Assert.That(receivedException, Is.EqualTo(exception));
+ Assert.That(result, Is.False);
+ }
+
+ [Test]
+ public void OnDirectoryChanging_WithDirectoryEvent_ShouldCallOnDirectoryChangingWithFullDirectoryPath()
+ {
+ var basePath = Path.GetFullPath("/foo/bar");
+ var directoryName = "test-directory";
+ var expectedPath = Path.Combine(basePath, directoryName);
+ var calledPath = string.Empty;
+ var fs = new MockFileSystem(null, basePath)
+ .OnDirectoryEvent(f => calledPath = f.Path);
+
+ fs.Directory.CreateDirectory(directoryName);
+
+ Assert.That(calledPath, Is.EqualTo(expectedPath));
+ }
+
+ [Test]
+ public void OnDirectoryChanging_WithFileEvent_ShouldNotBeCalled()
+ {
+ var basePath = Path.GetFullPath("/foo/bar");
+ var fileName = "test-directory";
+ bool isCalled = false;
+ var fs = new MockFileSystem(null, basePath)
+ .OnDirectoryEvent(f => isCalled = true);
+
+ fs.File.WriteAllText(fileName, "some content");
+
+ Assert.That(isCalled, Is.False);
+ }
+
+ [Test]
+ public void File_WriteAllText_NewFile_ShouldTriggerOnFileChangingWithCreatedType()
+ {
+ var fileName = "foo.txt";
+ MockFileEvent.FileEventType? receivedEventType = null;
+ var fs = new MockFileSystem()
+ .OnFileEvent(f => receivedEventType = f.EventType);
+
+ fs.File.WriteAllText(fileName, "some content");
+
+ Assert.That(receivedEventType, Is.EqualTo(MockFileEvent.FileEventType.Created));
+ }
+
+ [Test]
+ public void File_WriteAllText_ExistingFile_ShouldTriggerOnFileChangingWithUpdatedType()
+ {
+ var fileName = "foo.txt";
+ MockFileEvent.FileEventType? receivedEventType = null;
+ var fs = new MockFileSystem(new Dictionary
+ {
+ { fileName, new MockFileData("some content") }
+ }).OnFileEvent(f => receivedEventType = f.EventType);
+
+ fs.File.WriteAllText(fileName, "some content");
+
+ Assert.That(receivedEventType, Is.EqualTo(MockFileEvent.FileEventType.Updated));
+ }
+
+ [Test]
+ public void File_Delete_ShouldTriggerOnFileChangingWithDeletedType()
+ {
+ var fileName = "foo.txt";
+ MockFileEvent.FileEventType? receivedEventType = null;
+ var fs = new MockFileSystem(new Dictionary
+ {
+ { fileName, new MockFileData("some content") }
+ }).OnFileEvent(f => receivedEventType = f.EventType);
+
+ fs.File.Delete(fileName);
+
+ Assert.That(receivedEventType, Is.EqualTo(MockFileEvent.FileEventType.Deleted));
+ }
+
+ [Test]
+ public void File_Move_ShouldTriggerOnFileChangingWithDeletedAndCreatedTypes()
+ {
+ var fileName = "foo.txt";
+ var receivedEventTypes = new List();
+ var fs = new MockFileSystem(new Dictionary
+ {
+ { fileName, new MockFileData("some content") }
+ }).OnFileEvent(f => receivedEventTypes.Add(f.EventType));
+
+ fs.File.Move(fileName, "bar.txt");
+
+ Assert.That(receivedEventTypes, Contains.Item(MockFileEvent.FileEventType.Deleted));
+ Assert.That(receivedEventTypes, Contains.Item(MockFileEvent.FileEventType.Created));
+ }
+
+ [Test]
+ public void Directory_CreateDirectory_ShouldCallOnDirectoryChangingWithFullDirectoryPath()
+ {
+ var directoryName = "test-directory";
+ MockDirectoryEvent.DirectoryEventType? receivedEventType = null;
+ var fs = new MockFileSystem()
+ .OnDirectoryEvent(f => receivedEventType = f.EventType);
+
+ fs.Directory.CreateDirectory(directoryName);
+
+ Assert.That(receivedEventType, Is.EqualTo(MockDirectoryEvent.DirectoryEventType.Created));
+ }
+
+ [Test]
+ public void Directory_DeleteDirectory_ShouldCallOnDirectoryChangingWithFullDirectoryPath()
+ {
+ var directoryName = "test-directory";
+ MockDirectoryEvent.DirectoryEventType? receivedEventType = null;
+ var fs = new MockFileSystem(new Dictionary
+ {
+ { directoryName, new MockDirectoryData() }
+ }).OnDirectoryEvent(f => receivedEventType = f.EventType);
+
+ fs.Directory.Delete(directoryName);
+
+ Assert.That(receivedEventType, Is.EqualTo(MockDirectoryEvent.DirectoryEventType.Deleted));
+ }
+
+ [Test]
+ public void Directory_Move_ShouldTriggerOnDirectoryChangingWithDeletedAndCreatedTypes()
+ {
+ var fileName = "foo.txt";
+ var receivedEventTypes = new List();
+ var fs = new MockFileSystem(new Dictionary
+ {
+ { fileName, new MockDirectoryData() }
+ }).OnDirectoryEvent(f => receivedEventTypes.Add(f.EventType));
+
+ fs.Directory.Move(fileName, "bar.txt");
+
+ Assert.That(receivedEventTypes, Contains.Item(MockDirectoryEvent.DirectoryEventType.Deleted));
+ Assert.That(receivedEventTypes, Contains.Item(MockDirectoryEvent.DirectoryEventType.Created));
+ }
+ }
+}