From d9fa61e6ea877ee8118a46526f760e6d4f0bc7eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Breu=C3=9F=20Valentin?= Date: Sat, 21 Feb 2026 12:11:33 +0100 Subject: [PATCH 1/3] feat: add `InitializeFromRealDirectory` Support initializing a directory in the `MockFileSystem` from a real directory. --- .../FileSystemInitializerExtensions.cs | 39 +++++++++++ .../FileSystemInitializerExtensionsTests.cs | 70 ++++++++++++++++++- 2 files changed, 108 insertions(+), 1 deletion(-) diff --git a/Source/Testably.Abstractions.Testing/FileSystemInitializerExtensions.cs b/Source/Testably.Abstractions.Testing/FileSystemInitializerExtensions.cs index 5113b2a5b..20ccca984 100644 --- a/Source/Testably.Abstractions.Testing/FileSystemInitializerExtensions.cs +++ b/Source/Testably.Abstractions.Testing/FileSystemInitializerExtensions.cs @@ -108,6 +108,21 @@ public static void InitializeEmbeddedResourcesFromAssembly(this IFileSystem file } #pragma warning restore MA0051 // Method is too long + /// + /// Initializes the from the real into the + /// on the mock file system. + /// + /// + /// If no is set, the data is copied to the on + /// the . + /// + public static void InitializeFromRealDirectory(this IFileSystem fileSystem, + string sourceDirectory, string? targetDirectory = null) + { + using IDisposable release = fileSystem.IgnoreStatistics(); + CopyDirectory(fileSystem, sourceDirectory, targetDirectory ?? sourceDirectory); + } + /// /// Initializes the in the with test data. /// @@ -158,6 +173,30 @@ public static IDirectoryCleaner SetCurrentDirectoryToEmptyTemporaryDirectory( return new DirectoryCleaner(fileSystem, prefix, logger); } + private static void CopyDirectory( + IFileSystem fileSystem, string sourceDirectory, string targetDirectory) + { + if (!Directory.Exists(sourceDirectory)) + { + throw new NotSupportedException($"The directory '{sourceDirectory}' does not exist."); + } + + fileSystem.Directory.CreateDirectory(targetDirectory); + foreach (string? file in Directory.EnumerateFiles(sourceDirectory)) + { + string? fileName = Path.GetFileName(file); + fileSystem.File.WriteAllBytes(fileSystem.Path.Combine(targetDirectory, fileName), + File.ReadAllBytes(file)); + } + + foreach (string? directory in Directory.EnumerateDirectories(sourceDirectory)) + { + string? directoryName = Path.GetFileName(directory); + CopyDirectory(fileSystem, directory, + fileSystem.Path.Combine(targetDirectory, directoryName)); + } + } + private static void InitializeFileFromEmbeddedResource(this IFileSystem fileSystem, string path, Assembly assembly, diff --git a/Tests/Testably.Abstractions.Testing.Tests/FileSystemInitializerExtensionsTests.cs b/Tests/Testably.Abstractions.Testing.Tests/FileSystemInitializerExtensionsTests.cs index aaa1cccc2..218957891 100644 --- a/Tests/Testably.Abstractions.Testing.Tests/FileSystemInitializerExtensionsTests.cs +++ b/Tests/Testably.Abstractions.Testing.Tests/FileSystemInitializerExtensionsTests.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using aweXpect.Testably; +using System.Collections.Generic; using System.IO; using System.Linq; using System.Reflection; @@ -243,6 +244,73 @@ await That(result) .Contains(x => x.EndsWith("SubResourceFile1.txt", StringComparison.Ordinal)); } + [Fact] + public async Task InitializeFromRealDirectory_ShouldCopyFileToTargetDirectory() + { + MockFileSystem fileSystem = new(); + string tempPath = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()); + try + { + Directory.CreateDirectory(tempPath); + string filePath = Path.Combine(tempPath, "test.txt"); + File.WriteAllText(filePath, "Hello, World!"); + + fileSystem.InitializeFromRealDirectory(tempPath, "foo"); + + await That(fileSystem).HasDirectory("foo"); + await That(fileSystem).HasFile("foo/test.txt").WithContent("Hello, World!"); + } + finally + { + Directory.Delete(tempPath, true); + } + } + + [Fact] + public async Task + InitializeFromRealDirectory_ShouldRecursivelyCopyDirectoriesToTargetDirectory() + { + MockFileSystem fileSystem = new(); + string tempPath = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()); + try + { + Directory.CreateDirectory(tempPath); + Directory.CreateDirectory(Path.Combine(tempPath, "subdir")); + File.WriteAllText(Path.Combine(tempPath, "subdir", "test1.txt"), "foo"); + File.WriteAllText(Path.Combine(tempPath, "subdir", "test2.txt"), "bar"); + + fileSystem.InitializeFromRealDirectory(tempPath); + + await That(fileSystem).HasDirectory(fileSystem.Path.Combine(tempPath, "subdir")); + await That(fileSystem).HasFile(fileSystem.Path.Combine(tempPath, "subdir", "test1.txt")) + .WithContent("foo"); + await That(fileSystem).HasFile(fileSystem.Path.Combine(tempPath, "subdir", "test2.txt")) + .WithContent("bar"); + } + finally + { + Directory.Delete(tempPath, true); + } + } + + [Fact] + public async Task + InitializeFromRealDirectory_WhenDirectoryDoesNotExist_ShouldThrowNotSupportedException() + { + MockFileSystem fileSystem = new(); + string tempPath = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()); + while (Directory.Exists(tempPath)) + { + tempPath = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()); + } + + void Act() + => fileSystem.InitializeFromRealDirectory(tempPath, "foo"); + + await That(Act).Throws() + .WithMessage($"The directory '{tempPath}' does not exist."); + } + [Theory] [AutoData] public async Task InitializeIn_MissingDrive_ShouldCreateDrive(string directoryName) From 98c8e2f5fe3b463b794ac8742dc9b3076717eac1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Breu=C3=9F=20Valentin?= Date: Sat, 21 Feb 2026 12:15:13 +0100 Subject: [PATCH 2/3] Accept API changes --- .../Expected/Testably.Abstractions.Testing_net10.0.txt | 1 + .../Expected/Testably.Abstractions.Testing_net6.0.txt | 1 + .../Expected/Testably.Abstractions.Testing_net8.0.txt | 1 + .../Expected/Testably.Abstractions.Testing_net9.0.txt | 1 + .../Expected/Testably.Abstractions.Testing_netstandard2.0.txt | 1 + .../Expected/Testably.Abstractions.Testing_netstandard2.1.txt | 1 + 6 files changed, 6 insertions(+) diff --git a/Tests/Api/Testably.Abstractions.Api.Tests/Expected/Testably.Abstractions.Testing_net10.0.txt b/Tests/Api/Testably.Abstractions.Api.Tests/Expected/Testably.Abstractions.Testing_net10.0.txt index 8219bb8e5..921b4f81b 100644 --- a/Tests/Api/Testably.Abstractions.Api.Tests/Expected/Testably.Abstractions.Testing_net10.0.txt +++ b/Tests/Api/Testably.Abstractions.Api.Tests/Expected/Testably.Abstractions.Testing_net10.0.txt @@ -18,6 +18,7 @@ namespace Testably.Abstractions.Testing public static Testably.Abstractions.Testing.Initializer.IFileSystemInitializer Initialize(this TFileSystem fileSystem, System.Action? options = null) where TFileSystem : System.IO.Abstractions.IFileSystem { } public static void InitializeEmbeddedResourcesFromAssembly(this System.IO.Abstractions.IFileSystem fileSystem, string directoryPath, System.Reflection.Assembly assembly, string? relativePath = null, string searchPattern = "*", System.IO.SearchOption searchOption = 1) { } + public static void InitializeFromRealDirectory(this System.IO.Abstractions.IFileSystem fileSystem, string sourceDirectory, string? targetDirectory = null) { } public static Testably.Abstractions.Testing.Initializer.IFileSystemInitializer InitializeIn(this TFileSystem fileSystem, string basePath, System.Action? options = null) where TFileSystem : System.IO.Abstractions.IFileSystem { } public static Testably.Abstractions.Testing.Initializer.IDirectoryCleaner SetCurrentDirectoryToEmptyTemporaryDirectory(this System.IO.Abstractions.IFileSystem fileSystem, string? prefix = null, System.Action? logger = null) { } diff --git a/Tests/Api/Testably.Abstractions.Api.Tests/Expected/Testably.Abstractions.Testing_net6.0.txt b/Tests/Api/Testably.Abstractions.Api.Tests/Expected/Testably.Abstractions.Testing_net6.0.txt index e9819ffe9..8891e3718 100644 --- a/Tests/Api/Testably.Abstractions.Api.Tests/Expected/Testably.Abstractions.Testing_net6.0.txt +++ b/Tests/Api/Testably.Abstractions.Api.Tests/Expected/Testably.Abstractions.Testing_net6.0.txt @@ -18,6 +18,7 @@ namespace Testably.Abstractions.Testing public static Testably.Abstractions.Testing.Initializer.IFileSystemInitializer Initialize(this TFileSystem fileSystem, System.Action? options = null) where TFileSystem : System.IO.Abstractions.IFileSystem { } public static void InitializeEmbeddedResourcesFromAssembly(this System.IO.Abstractions.IFileSystem fileSystem, string directoryPath, System.Reflection.Assembly assembly, string? relativePath = null, string searchPattern = "*", System.IO.SearchOption searchOption = 1) { } + public static void InitializeFromRealDirectory(this System.IO.Abstractions.IFileSystem fileSystem, string sourceDirectory, string? targetDirectory = null) { } public static Testably.Abstractions.Testing.Initializer.IFileSystemInitializer InitializeIn(this TFileSystem fileSystem, string basePath, System.Action? options = null) where TFileSystem : System.IO.Abstractions.IFileSystem { } public static Testably.Abstractions.Testing.Initializer.IDirectoryCleaner SetCurrentDirectoryToEmptyTemporaryDirectory(this System.IO.Abstractions.IFileSystem fileSystem, string? prefix = null, System.Action? logger = null) { } diff --git a/Tests/Api/Testably.Abstractions.Api.Tests/Expected/Testably.Abstractions.Testing_net8.0.txt b/Tests/Api/Testably.Abstractions.Api.Tests/Expected/Testably.Abstractions.Testing_net8.0.txt index a8ed98732..7ade5f592 100644 --- a/Tests/Api/Testably.Abstractions.Api.Tests/Expected/Testably.Abstractions.Testing_net8.0.txt +++ b/Tests/Api/Testably.Abstractions.Api.Tests/Expected/Testably.Abstractions.Testing_net8.0.txt @@ -18,6 +18,7 @@ namespace Testably.Abstractions.Testing public static Testably.Abstractions.Testing.Initializer.IFileSystemInitializer Initialize(this TFileSystem fileSystem, System.Action? options = null) where TFileSystem : System.IO.Abstractions.IFileSystem { } public static void InitializeEmbeddedResourcesFromAssembly(this System.IO.Abstractions.IFileSystem fileSystem, string directoryPath, System.Reflection.Assembly assembly, string? relativePath = null, string searchPattern = "*", System.IO.SearchOption searchOption = 1) { } + public static void InitializeFromRealDirectory(this System.IO.Abstractions.IFileSystem fileSystem, string sourceDirectory, string? targetDirectory = null) { } public static Testably.Abstractions.Testing.Initializer.IFileSystemInitializer InitializeIn(this TFileSystem fileSystem, string basePath, System.Action? options = null) where TFileSystem : System.IO.Abstractions.IFileSystem { } public static Testably.Abstractions.Testing.Initializer.IDirectoryCleaner SetCurrentDirectoryToEmptyTemporaryDirectory(this System.IO.Abstractions.IFileSystem fileSystem, string? prefix = null, System.Action? logger = null) { } diff --git a/Tests/Api/Testably.Abstractions.Api.Tests/Expected/Testably.Abstractions.Testing_net9.0.txt b/Tests/Api/Testably.Abstractions.Api.Tests/Expected/Testably.Abstractions.Testing_net9.0.txt index 94c965e15..ff084f748 100644 --- a/Tests/Api/Testably.Abstractions.Api.Tests/Expected/Testably.Abstractions.Testing_net9.0.txt +++ b/Tests/Api/Testably.Abstractions.Api.Tests/Expected/Testably.Abstractions.Testing_net9.0.txt @@ -18,6 +18,7 @@ namespace Testably.Abstractions.Testing public static Testably.Abstractions.Testing.Initializer.IFileSystemInitializer Initialize(this TFileSystem fileSystem, System.Action? options = null) where TFileSystem : System.IO.Abstractions.IFileSystem { } public static void InitializeEmbeddedResourcesFromAssembly(this System.IO.Abstractions.IFileSystem fileSystem, string directoryPath, System.Reflection.Assembly assembly, string? relativePath = null, string searchPattern = "*", System.IO.SearchOption searchOption = 1) { } + public static void InitializeFromRealDirectory(this System.IO.Abstractions.IFileSystem fileSystem, string sourceDirectory, string? targetDirectory = null) { } public static Testably.Abstractions.Testing.Initializer.IFileSystemInitializer InitializeIn(this TFileSystem fileSystem, string basePath, System.Action? options = null) where TFileSystem : System.IO.Abstractions.IFileSystem { } public static Testably.Abstractions.Testing.Initializer.IDirectoryCleaner SetCurrentDirectoryToEmptyTemporaryDirectory(this System.IO.Abstractions.IFileSystem fileSystem, string? prefix = null, System.Action? logger = null) { } diff --git a/Tests/Api/Testably.Abstractions.Api.Tests/Expected/Testably.Abstractions.Testing_netstandard2.0.txt b/Tests/Api/Testably.Abstractions.Api.Tests/Expected/Testably.Abstractions.Testing_netstandard2.0.txt index 1a67f362d..e50204939 100644 --- a/Tests/Api/Testably.Abstractions.Api.Tests/Expected/Testably.Abstractions.Testing_netstandard2.0.txt +++ b/Tests/Api/Testably.Abstractions.Api.Tests/Expected/Testably.Abstractions.Testing_netstandard2.0.txt @@ -14,6 +14,7 @@ namespace Testably.Abstractions.Testing public static Testably.Abstractions.Testing.Initializer.IFileSystemInitializer Initialize(this TFileSystem fileSystem, System.Action? options = null) where TFileSystem : System.IO.Abstractions.IFileSystem { } public static void InitializeEmbeddedResourcesFromAssembly(this System.IO.Abstractions.IFileSystem fileSystem, string directoryPath, System.Reflection.Assembly assembly, string? relativePath = null, string searchPattern = "*", System.IO.SearchOption searchOption = 1) { } + public static void InitializeFromRealDirectory(this System.IO.Abstractions.IFileSystem fileSystem, string sourceDirectory, string? targetDirectory = null) { } public static Testably.Abstractions.Testing.Initializer.IFileSystemInitializer InitializeIn(this TFileSystem fileSystem, string basePath, System.Action? options = null) where TFileSystem : System.IO.Abstractions.IFileSystem { } public static Testably.Abstractions.Testing.Initializer.IDirectoryCleaner SetCurrentDirectoryToEmptyTemporaryDirectory(this System.IO.Abstractions.IFileSystem fileSystem, string? prefix = null, System.Action? logger = null) { } diff --git a/Tests/Api/Testably.Abstractions.Api.Tests/Expected/Testably.Abstractions.Testing_netstandard2.1.txt b/Tests/Api/Testably.Abstractions.Api.Tests/Expected/Testably.Abstractions.Testing_netstandard2.1.txt index 95c397d7f..ebd68b1f4 100644 --- a/Tests/Api/Testably.Abstractions.Api.Tests/Expected/Testably.Abstractions.Testing_netstandard2.1.txt +++ b/Tests/Api/Testably.Abstractions.Api.Tests/Expected/Testably.Abstractions.Testing_netstandard2.1.txt @@ -14,6 +14,7 @@ namespace Testably.Abstractions.Testing public static Testably.Abstractions.Testing.Initializer.IFileSystemInitializer Initialize(this TFileSystem fileSystem, System.Action? options = null) where TFileSystem : System.IO.Abstractions.IFileSystem { } public static void InitializeEmbeddedResourcesFromAssembly(this System.IO.Abstractions.IFileSystem fileSystem, string directoryPath, System.Reflection.Assembly assembly, string? relativePath = null, string searchPattern = "*", System.IO.SearchOption searchOption = 1) { } + public static void InitializeFromRealDirectory(this System.IO.Abstractions.IFileSystem fileSystem, string sourceDirectory, string? targetDirectory = null) { } public static Testably.Abstractions.Testing.Initializer.IFileSystemInitializer InitializeIn(this TFileSystem fileSystem, string basePath, System.Action? options = null) where TFileSystem : System.IO.Abstractions.IFileSystem { } public static Testably.Abstractions.Testing.Initializer.IDirectoryCleaner SetCurrentDirectoryToEmptyTemporaryDirectory(this System.IO.Abstractions.IFileSystem fileSystem, string? prefix = null, System.Action? logger = null) { } From 2eb09c0efbfe6483e548feb73fd6e56b2bdc4c2e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Breu=C3=9F=20Valentin?= Date: Sat, 21 Feb 2026 12:29:29 +0100 Subject: [PATCH 3/3] Fix review issues --- .../FileSystemInitializerExtensions.cs | 38 +++++++++++++----- .../FileSystemInitializerExtensionsTests.cs | 39 ++++++++++++++++++- 2 files changed, 66 insertions(+), 11 deletions(-) diff --git a/Source/Testably.Abstractions.Testing/FileSystemInitializerExtensions.cs b/Source/Testably.Abstractions.Testing/FileSystemInitializerExtensions.cs index 20ccca984..ca4795431 100644 --- a/Source/Testably.Abstractions.Testing/FileSystemInitializerExtensions.cs +++ b/Source/Testably.Abstractions.Testing/FileSystemInitializerExtensions.cs @@ -110,17 +110,36 @@ public static void InitializeEmbeddedResourcesFromAssembly(this IFileSystem file /// /// Initializes the from the real into the - /// on the mock file system. + /// on the provided file system. /// + /// The target file system to which the data is copied. + /// + /// The source directory on the real file system that is copied to the + /// . + /// + /// + /// The target directory on the to which the data is copied to.
+ /// If no is set, is used instead. + /// /// - /// If no is set, the data is copied to the on - /// the . + /// Warning:
+ /// This method will recursively copy the content of all files and directories from the + /// to the . With large files, this can be very + /// resource intensive!
///
public static void InitializeFromRealDirectory(this IFileSystem fileSystem, string sourceDirectory, string? targetDirectory = null) { using IDisposable release = fileSystem.IgnoreStatistics(); - CopyDirectory(fileSystem, sourceDirectory, targetDirectory ?? sourceDirectory); + targetDirectory ??= sourceDirectory; + if (fileSystem.Path.IsPathRooted(targetDirectory) && + fileSystem is MockFileSystem mockFileSystem) + { + string? drive = fileSystem.Path.GetPathRoot(targetDirectory); + mockFileSystem.WithDrive(drive); + } + + CopyDirectory(fileSystem, sourceDirectory, targetDirectory); } /// @@ -178,20 +197,21 @@ private static void CopyDirectory( { if (!Directory.Exists(sourceDirectory)) { - throw new NotSupportedException($"The directory '{sourceDirectory}' does not exist."); + throw new DirectoryNotFoundException( + $"The directory '{sourceDirectory}' does not exist."); } fileSystem.Directory.CreateDirectory(targetDirectory); - foreach (string? file in Directory.EnumerateFiles(sourceDirectory)) + foreach (string file in Directory.EnumerateFiles(sourceDirectory)) { - string? fileName = Path.GetFileName(file); + string fileName = Path.GetFileName(file); fileSystem.File.WriteAllBytes(fileSystem.Path.Combine(targetDirectory, fileName), File.ReadAllBytes(file)); } - foreach (string? directory in Directory.EnumerateDirectories(sourceDirectory)) + foreach (string directory in Directory.EnumerateDirectories(sourceDirectory)) { - string? directoryName = Path.GetFileName(directory); + string directoryName = Path.GetFileName(directory); CopyDirectory(fileSystem, directory, fileSystem.Path.Combine(targetDirectory, directoryName)); } diff --git a/Tests/Testably.Abstractions.Testing.Tests/FileSystemInitializerExtensionsTests.cs b/Tests/Testably.Abstractions.Testing.Tests/FileSystemInitializerExtensionsTests.cs index 218957891..70859a229 100644 --- a/Tests/Testably.Abstractions.Testing.Tests/FileSystemInitializerExtensionsTests.cs +++ b/Tests/Testably.Abstractions.Testing.Tests/FileSystemInitializerExtensionsTests.cs @@ -244,6 +244,41 @@ await That(result) .Contains(x => x.EndsWith("SubResourceFile1.txt", StringComparison.Ordinal)); } + [Theory] + [AutoData] + public async Task InitializeFromRealDirectory_MissingDrive_ShouldCreateDrive( + string directoryName) + { + Skip.IfNot(Test.RunsOnWindows); + + string tempPath = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()); + try + { + Directory.CreateDirectory(tempPath); + MockFileSystem sut = new(); + IDriveInfo[] drives = sut.DriveInfo.GetDrives(); + for (char c = 'D'; c <= 'Z'; c++) + { + if (drives.Any(d => d.Name.StartsWith($"{c}", StringComparison.Ordinal))) + { + continue; + } + + directoryName = Path.Combine($"{c}:\\", directoryName); + break; + } + + sut.InitializeFromRealDirectory(tempPath, directoryName); + + await That(sut.Directory.Exists(directoryName)).IsTrue(); + await That(sut.DriveInfo.GetDrives()).HasCount(drives.Length + 1); + } + finally + { + Directory.Delete(tempPath, true); + } + } + [Fact] public async Task InitializeFromRealDirectory_ShouldCopyFileToTargetDirectory() { @@ -295,7 +330,7 @@ await That(fileSystem).HasFile(fileSystem.Path.Combine(tempPath, "subdir", "test [Fact] public async Task - InitializeFromRealDirectory_WhenDirectoryDoesNotExist_ShouldThrowNotSupportedException() + InitializeFromRealDirectory_WhenDirectoryDoesNotExist_ShouldThrowDirectoryNotFoundException() { MockFileSystem fileSystem = new(); string tempPath = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()); @@ -307,7 +342,7 @@ public async Task void Act() => fileSystem.InitializeFromRealDirectory(tempPath, "foo"); - await That(Act).Throws() + await That(Act).Throws() .WithMessage($"The directory '{tempPath}' does not exist."); }