diff --git a/Source/Testably.Abstractions.FluentAssertions/DirectoryAssertions.cs b/Source/Testably.Abstractions.FluentAssertions/DirectoryAssertions.cs index 88fcc48..9de56b5 100644 --- a/Source/Testably.Abstractions.FluentAssertions/DirectoryAssertions.cs +++ b/Source/Testably.Abstractions.FluentAssertions/DirectoryAssertions.cs @@ -17,36 +17,45 @@ internal DirectoryAssertions(IDirectoryInfo? instance) } /// - /// Asserts that the directory contains exactly one file matching the given . + /// Asserts that the current directory has at least directories which match the + /// . /// - public AndWhichConstraint HasSingleFileMatching( - string searchPattern = "*", string because = "", params object[] becauseArgs) + public AndConstraint HasDirectoriesMatching( + string searchPattern = "*", + int minimumCount = 1, + string because = "", + params object[] becauseArgs) { Execute.Assertion .WithDefaultIdentifier(Identifier) .BecauseOf(because, becauseArgs) .ForCondition(Subject != null) .FailWith( - "You can't assert a directory having a given file if it is null") + "You can't assert a directory having directories if the DirectoryInfo is null.") .Then .ForCondition(!string.IsNullOrEmpty(searchPattern)) .FailWith( - "You can't assert a directory having a given file if you don't pass a proper name") + "You can't assert a directory having directories if you don't pass a proper search pattern.") .Then .Given(() => Subject!) .ForCondition(directoryInfo - => directoryInfo.GetFiles(searchPattern).Length == 1) + => directoryInfo.GetDirectories(searchPattern).Length >= minimumCount) .FailWith( - "Expected {context} {1} to contain exactly one file matching {0}{reason}, but found {2}.", + $"Expected {{context}} {{1}} to contain at least {(minimumCount == 1 ? "one directory" : $"{minimumCount} directories")} matching {{0}}{{reason}}, but {(minimumCount == 1 ? "none was" : "only {2} were")} found.", _ => searchPattern, directoryInfo => directoryInfo.Name, - directoryInfo => directoryInfo.GetFiles(searchPattern).Length); - - return new AndWhichConstraint( - new FileSystemAssertions(Subject!.FileSystem), - new FileAssertions(Subject!.GetFiles(searchPattern).Single())); + directoryInfo => directoryInfo.GetDirectories(searchPattern).Length); + + return new AndConstraint(this); } + /// + /// Asserts that the current directory has at least one directory which matches the . + /// + public AndConstraint HasDirectoryMatching( + string searchPattern = "*", string because = "", params object[] becauseArgs) + => HasDirectoriesMatching(searchPattern, 1, because, becauseArgs); + /// /// Asserts that the current directory has at least one file which matches the . /// @@ -69,11 +78,11 @@ public AndConstraint HasFilesMatching( .BecauseOf(because, becauseArgs) .ForCondition(Subject != null) .FailWith( - "You can't assert a directory having files if the DirectoryInfo is null") + "You can't assert a directory having files if the DirectoryInfo is null.") .Then .ForCondition(!string.IsNullOrEmpty(searchPattern)) .FailWith( - "You can't assert a directory having files if you don't pass a proper name") + "You can't assert a directory having files if you don't pass a proper search pattern.") .Then .Given(() => Subject!) .ForCondition(directoryInfo @@ -86,4 +95,66 @@ public AndConstraint HasFilesMatching( return new AndConstraint(this); } + + /// + /// Asserts that the directory contains exactly one directory matching the given . + /// + public AndWhichConstraint HasSingleDirectoryMatching( + string searchPattern = "*", string because = "", params object[] becauseArgs) + { + Execute.Assertion + .WithDefaultIdentifier(Identifier) + .BecauseOf(because, becauseArgs) + .ForCondition(Subject != null) + .FailWith( + "You can't assert a directory having a given directory if it is null.") + .Then + .ForCondition(!string.IsNullOrEmpty(searchPattern)) + .FailWith( + "You can't assert a directory having a given directory if you don't pass a proper search pattern.") + .Then + .Given(() => Subject!) + .ForCondition(directoryInfo + => directoryInfo.GetDirectories(searchPattern).Length == 1) + .FailWith( + "Expected {context} {1} to contain exactly one directory matching {0}{reason}, but found {2}.", + _ => searchPattern, + directoryInfo => directoryInfo.Name, + directoryInfo => directoryInfo.GetDirectories(searchPattern).Length); + + return new AndWhichConstraint( + new FileSystemAssertions(Subject!.FileSystem), + new DirectoryAssertions(Subject!.GetDirectories(searchPattern).Single())); + } + + /// + /// Asserts that the directory contains exactly one file matching the given . + /// + public AndWhichConstraint HasSingleFileMatching( + string searchPattern = "*", string because = "", params object[] becauseArgs) + { + Execute.Assertion + .WithDefaultIdentifier(Identifier) + .BecauseOf(because, becauseArgs) + .ForCondition(Subject != null) + .FailWith( + "You can't assert a directory having a given file if it is null.") + .Then + .ForCondition(!string.IsNullOrEmpty(searchPattern)) + .FailWith( + "You can't assert a directory having a given file if you don't pass a proper search pattern.") + .Then + .Given(() => Subject!) + .ForCondition(directoryInfo + => directoryInfo.GetFiles(searchPattern).Length == 1) + .FailWith( + "Expected {context} {1} to contain exactly one file matching {0}{reason}, but found {2}.", + _ => searchPattern, + directoryInfo => directoryInfo.Name, + directoryInfo => directoryInfo.GetFiles(searchPattern).Length); + + return new AndWhichConstraint( + new FileSystemAssertions(Subject!.FileSystem), + new FileAssertions(Subject!.GetFiles(searchPattern).Single())); + } } diff --git a/Source/Testably.Abstractions.FluentAssertions/DirectoryInfoAssertions.cs b/Source/Testably.Abstractions.FluentAssertions/DirectoryInfoAssertions.cs index 4575f7e..eb0d9ae 100644 --- a/Source/Testably.Abstractions.FluentAssertions/DirectoryInfoAssertions.cs +++ b/Source/Testably.Abstractions.FluentAssertions/DirectoryInfoAssertions.cs @@ -14,6 +14,16 @@ internal DirectoryInfoAssertions(IDirectoryInfo? instance) { } + /// + /// Asserts that the current directory has at least one directory which matches the . + /// + public AndConstraint HaveDirectoryMatching( + string searchPattern = "*", string because = "", params object[] becauseArgs) + { + new DirectoryAssertions(Subject).HasDirectoryMatching(searchPattern, because, becauseArgs); + return new AndConstraint(this); + } + /// /// Asserts that the current directory has at least one file which matches the . /// @@ -24,12 +34,23 @@ public AndConstraint HaveFileMatching( return new AndConstraint(this); } + /// + /// Asserts that the directory contains exactly one directory matching the given . + /// + public AndWhichConstraint HaveSingleDirectory( + string searchPattern = "*", string because = "", params object[] becauseArgs) + { + return new DirectoryAssertions(Subject).HasSingleDirectoryMatching(searchPattern, because, + becauseArgs); + } + /// /// Asserts that the directory contains exactly one file matching the given . /// public AndWhichConstraint HaveSingleFile( string searchPattern = "*", string because = "", params object[] becauseArgs) { - return new DirectoryAssertions(Subject).HasSingleFileMatching(searchPattern, because, becauseArgs); + return new DirectoryAssertions(Subject).HasSingleFileMatching(searchPattern, because, + becauseArgs); } } diff --git a/Source/Testably.Abstractions.FluentAssertions/FileAssertions.cs b/Source/Testably.Abstractions.FluentAssertions/FileAssertions.cs index f898883..ff843c1 100644 --- a/Source/Testably.Abstractions.FluentAssertions/FileAssertions.cs +++ b/Source/Testably.Abstractions.FluentAssertions/FileAssertions.cs @@ -29,7 +29,7 @@ public AndConstraint DoesNotHaveAttribute( .BecauseOf(because, becauseArgs) .ForCondition(Subject != null) .FailWith( - $"You can't assert that the file does not have attribute {attribute} if it is null") + $"You can't assert that the file does not have attribute {attribute} if it is null.") .Then .Given(() => Subject!) .ForCondition(fileInfo => !fileInfo.Attributes.HasFlag(attribute)) @@ -51,7 +51,7 @@ public AndConstraint HasAttribute( .BecauseOf(because, becauseArgs) .ForCondition(Subject != null) .FailWith( - $"You can't assert that the file has attribute {attribute} if it is null") + $"You can't assert that the file has attribute {attribute} if it is null.") .Then .Given(() => Subject!) .ForCondition(fileInfo => fileInfo.Attributes.HasFlag(attribute)) @@ -73,7 +73,7 @@ public AndConstraint HasContent( .BecauseOf(because, becauseArgs) .ForCondition(Subject != null) .FailWith( - "You can't assert the content of a file if the FileInfo is null") + "You can't assert the content of a file if the FileInfo is null.") .Then .Given(() => Subject!) .ForCondition(fileInfo => fileInfo.FileSystem.File @@ -97,7 +97,7 @@ public AndConstraint HasContent( .BecauseOf(because, becauseArgs) .ForCondition(Subject != null) .FailWith( - "You can't assert the content of a file if the FileInfo is null") + "You can't assert the content of a file if the FileInfo is null.") .Then .Given(() => Subject!) .ForCondition(fileInfo => pattern.Matches( @@ -121,7 +121,7 @@ public AndConstraint HasContent( .BecauseOf(because, becauseArgs) .ForCondition(Subject != null) .FailWith( - "You can't assert the content of a file if the FileInfo is null") + "You can't assert the content of a file if the FileInfo is null.") .Then .Given(() => Subject!) .ForCondition(fileInfo => pattern.Matches( @@ -144,7 +144,7 @@ public AndConstraint IsNotReadOnly( .BecauseOf(because, becauseArgs) .ForCondition(Subject != null) .FailWith( - "You can't assert that the file is not read-only if it is null") + "You can't assert that the file is not read-only if it is null.") .Then .Given(() => Subject!) .ForCondition(fileInfo => !fileInfo.IsReadOnly) @@ -166,7 +166,7 @@ public AndConstraint IsReadOnly( .BecauseOf(because, becauseArgs) .ForCondition(Subject != null) .FailWith( - "You can't assert that the file is read-only if it is null") + "You can't assert that the file is read-only if it is null.") .Then .Given(() => Subject!) .ForCondition(fileInfo => fileInfo.IsReadOnly) diff --git a/Source/Testably.Abstractions.FluentAssertions/FileSystemAssertions.cs b/Source/Testably.Abstractions.FluentAssertions/FileSystemAssertions.cs index eaa1e38..666081b 100644 --- a/Source/Testably.Abstractions.FluentAssertions/FileSystemAssertions.cs +++ b/Source/Testably.Abstractions.FluentAssertions/FileSystemAssertions.cs @@ -24,7 +24,7 @@ public AndWhichConstraint HaveDirecto .WithDefaultIdentifier(Identifier) .BecauseOf(because, becauseArgs) .ForCondition(!string.IsNullOrEmpty(path)) - .FailWith("You can't assert that a directory exists if you don't pass a proper name.") + .FailWith("You can't assert that a directory exists if you don't pass a proper path.") .Then .Given(() => Subject.DirectoryInfo.New(path)) .ForCondition(directoryInfo => directoryInfo.Exists) @@ -46,7 +46,7 @@ public AndWhichConstraint HaveFile( .WithDefaultIdentifier(Identifier) .BecauseOf(because, becauseArgs) .ForCondition(!string.IsNullOrEmpty(path)) - .FailWith("You can't assert that a file exists if you don't pass a proper name.") + .FailWith("You can't assert that a file exists if you don't pass a proper path.") .Then .Given(() => Subject.FileInfo.New(path)) .ForCondition(fileInfo => fileInfo.Exists) @@ -69,7 +69,7 @@ public AndWhichConstraint NotHaveDire .BecauseOf(because, becauseArgs) .ForCondition(!string.IsNullOrEmpty(path)) .FailWith( - "You can't assert that a directory does not exist if you don't pass a proper name.") + "You can't assert that a directory does not exist if you don't pass a proper path.") .Then .Given(() => Subject.DirectoryInfo.New(path)) .ForCondition(directoryInfo => !directoryInfo.Exists) @@ -92,7 +92,7 @@ public AndWhichConstraint NotHaveFile( .BecauseOf(because, becauseArgs) .ForCondition(!string.IsNullOrEmpty(path)) .FailWith( - "You can't assert that a file does not exist if you don't pass a proper name.") + "You can't assert that a file does not exist if you don't pass a proper path.") .Then .Given(() => Subject.FileInfo.New(path)) .ForCondition(fileInfo => !fileInfo.Exists) diff --git a/Source/Testably.Abstractions.FluentAssertions/FileSystemInfoAssertions.cs b/Source/Testably.Abstractions.FluentAssertions/FileSystemInfoAssertions.cs index 4018a91..546c73b 100644 --- a/Source/Testably.Abstractions.FluentAssertions/FileSystemInfoAssertions.cs +++ b/Source/Testably.Abstractions.FluentAssertions/FileSystemInfoAssertions.cs @@ -26,7 +26,7 @@ public AndConstraint Exist( .WithDefaultIdentifier(Identifier) .BecauseOf(because, becauseArgs) .ForCondition(Subject != null) - .FailWith("You can't assert that the {context} exists if it is null") + .FailWith("You can't assert that the {context} exists if it is null.") .Then .Given(() => Subject!) .ForCondition(fileSystemInfo => fileSystemInfo.Exists) diff --git a/Tests/Testably.Abstractions.FluentAssertions.Tests/DirectoryAssertionsTests.cs b/Tests/Testably.Abstractions.FluentAssertions.Tests/DirectoryAssertionsTests.cs index 50dfbf2..a99e119 100644 --- a/Tests/Testably.Abstractions.FluentAssertions.Tests/DirectoryAssertionsTests.cs +++ b/Tests/Testably.Abstractions.FluentAssertions.Tests/DirectoryAssertionsTests.cs @@ -8,6 +8,98 @@ namespace Testably.Abstractions.FluentAssertions.Tests; public class DirectoryAssertionsTests { + [Theory] + [InlineAutoData(3, 5)] + [InlineAutoData(1, 2)] + public void HasDirectoriesMatching_WithoutTooLittleDirectories_ShouldThrow( + int matchingCount, + int expectedCount, + string directoryName, + string directoryNamePrefix, + string because) + { + MockFileSystem fileSystem = new(); + fileSystem.Initialize() + .WithSubdirectory(directoryName); + for (int i = 0; i < matchingCount; i++) + { + fileSystem.Directory.CreateDirectory( + fileSystem.Path.Combine(directoryName, $"{directoryNamePrefix}-{i}")); + } + + DirectoryAssertions? sut = fileSystem.Should().HaveDirectory(directoryName).Which; + + Exception? exception = Record.Exception(() => + { + sut.HasDirectoriesMatching($"{directoryNamePrefix}*", expectedCount, because); + }); + + exception.Should().NotBeNull(); + exception!.Message.Should() + .Be( + $"Expected directory \"{directoryName}\" to contain at least {expectedCount} directories matching \"{directoryNamePrefix}*\" {because}, but only {matchingCount} were found."); + } + + [Theory] + [InlineAutoData(null)] + [InlineAutoData("")] + public void HasDirectoryMatching_InvalidDirectoryName_ShouldThrow(string? invalidDirectoryName, + string because) + { + MockFileSystem fileSystem = new(); + fileSystem.Initialize() + .WithSubdirectory("foo"); + DirectoryAssertions? sut = fileSystem.Should().HaveDirectory("foo").Which; + + Exception? exception = Record.Exception(() => + { + sut.HasDirectoryMatching(invalidDirectoryName!, because); + }); + + exception.Should().NotBeNull(); + exception!.Message.Should().NotBeNullOrEmpty(); + exception.Message.Should().NotContain(because); + } + + [Theory] + [AutoData] + public void HasDirectoryMatching_WithMatchingDirectory_ShouldNotThrow( + string directoryName, + string subdirectoryName) + { + MockFileSystem fileSystem = new(); + fileSystem.Initialize() + .WithSubdirectory(directoryName).Initialized(d => d + .WithSubdirectory(subdirectoryName)); + DirectoryAssertions? sut = fileSystem.Should().HaveDirectory(directoryName).Which; + + sut.HasDirectoryMatching(subdirectoryName); + } + + [Theory] + [AutoData] + public void HasDirectoryMatching_WithoutMatchingDirectory_ShouldThrow( + string directoryName, + string subdirectoryName, + string because) + { + MockFileSystem fileSystem = new(); + fileSystem.Initialize() + .WithSubdirectory(directoryName).Initialized(d => d + .WithSubdirectory("not-matching-directory")); + DirectoryAssertions? sut = fileSystem.Should().HaveDirectory(directoryName).Which; + + Exception? exception = Record.Exception(() => + { + sut.HasDirectoryMatching(subdirectoryName, because); + }); + + exception.Should().NotBeNull(); + exception!.Message.Should() + .Be( + $"Expected directory \"{directoryName}\" to contain at least one directory matching \"{subdirectoryName}\" {because}, but none was found."); + } + [Theory] [InlineAutoData(null)] [InlineAutoData("")] @@ -100,6 +192,92 @@ public void HasFilesMatching_WithoutTooLittleFiles_ShouldThrow( $"Expected directory \"{directoryName}\" to contain at least {expectedCount} files matching \"{fileNamePrefix}*\" {because}, but only {matchingCount} were found."); } + [Theory] + [InlineAutoData(null)] + [InlineAutoData("")] + public void HasSingleDirectoryMatching_InvalidDirectoryName_ShouldThrow( + string? invalidDirectoryName, + string because) + { + MockFileSystem fileSystem = new(); + fileSystem.Initialize() + .WithSubdirectory("foo"); + DirectoryAssertions? sut = fileSystem.Should().HaveDirectory("foo").Which; + + Exception? exception = Record.Exception(() => + { + sut.HasSingleDirectoryMatching(invalidDirectoryName!, because); + }); + + exception.Should().NotBeNull(); + exception!.Message.Should().NotBeNullOrEmpty(); + exception.Message.Should().NotContain(because); + } + + [Theory] + [AutoData] + public void HasSingleDirectoryMatching_WithMatchingDirectory_ShouldNotThrow( + string directoryName, + string subdirectoryName) + { + MockFileSystem fileSystem = new(); + fileSystem.Initialize() + .WithSubdirectory(directoryName).Initialized(d => d + .WithSubdirectory(subdirectoryName)); + DirectoryAssertions? sut = fileSystem.Should().HaveDirectory(directoryName).Which; + + sut.HasSingleDirectoryMatching(subdirectoryName); + } + + [Theory] + [AutoData] + public void HasSingleDirectoryMatching_WithMultipleMatchingDirectory_ShouldThrow( + string directoryName, + string subdirectoryName, + string because) + { + MockFileSystem fileSystem = new(); + fileSystem.Initialize() + .WithSubdirectory(directoryName).Initialized(d => d + .WithSubdirectory($"{subdirectoryName}-1.txt") + .WithSubdirectory($"{subdirectoryName}-2.txt")); + DirectoryAssertions? sut = fileSystem.Should().HaveDirectory(directoryName).Which; + + Exception? exception = Record.Exception(() => + { + sut.HasSingleDirectoryMatching($"{subdirectoryName}*", because); + }); + + exception.Should().NotBeNull(); + exception!.Message.Should() + .Be( + $"Expected directory \"{directoryName}\" to contain exactly one directory matching \"{subdirectoryName}*\" {because}, but found 2."); + } + + [Theory] + [AutoData] + public void HasSingleDirectoryMatching_WithoutMatchingDirectory_ShouldThrow( + string directoryName, + string subdirectoryName, + string because) + { + MockFileSystem fileSystem = new(); + fileSystem.Initialize() + .WithSubdirectory(directoryName).Initialized(d => d + .WithSubdirectory("not-matching-directory")); + DirectoryAssertions? sut = fileSystem.Should().HaveDirectory(directoryName).Which; + + Exception? exception = Record.Exception(() => + { + sut.HasSingleDirectoryMatching(subdirectoryName, because); + }); + + exception.Should().NotBeNull(); + exception!.Message.Should() + .Be( + $"Expected directory \"{directoryName}\" to contain exactly one directory matching \"{subdirectoryName}\" {because}, but found 0."); + } + [Theory] [InlineAutoData(null)] [InlineAutoData("")] diff --git a/Tests/Testably.Abstractions.FluentAssertions.Tests/DirectoryInfoAssertionsTests.cs b/Tests/Testably.Abstractions.FluentAssertions.Tests/DirectoryInfoAssertionsTests.cs index f1fbf60..7cd0a80 100644 --- a/Tests/Testably.Abstractions.FluentAssertions.Tests/DirectoryInfoAssertionsTests.cs +++ b/Tests/Testably.Abstractions.FluentAssertions.Tests/DirectoryInfoAssertionsTests.cs @@ -9,6 +9,82 @@ namespace Testably.Abstractions.FluentAssertions.Tests; public class DirectoryInfoAssertionsTests { + [Theory] + [InlineAutoData(null)] + [InlineAutoData("")] + public void HaveDirectoryMatching_InvalidDirectoryName_ShouldThrow(string? invalidDirectoryName, + string because) + { + MockFileSystem fileSystem = new(); + fileSystem.Initialize() + .WithSubdirectory("foo"); + IDirectoryInfo sut = fileSystem.DirectoryInfo.New("foo"); + + Exception? exception = Record.Exception(() => + { + sut.Should().HaveDirectoryMatching(invalidDirectoryName!, because); + }); + + exception.Should().NotBeNull(); + exception!.Message.Should().NotBeNullOrEmpty(); + exception.Message.Should().NotContain(because); + } + + [Theory] + [AutoData] + public void HaveDirectoryMatching_Null_ShouldThrow(string because) + { + IDirectoryInfo? sut = null; + + Exception? exception = Record.Exception(() => + { + sut.Should().HaveDirectoryMatching(because: because); + }); + + exception.Should().NotBeNull(); + exception!.Message.Should().Contain("null"); + exception.Message.Should().NotContain(because); + } + + [Theory] + [AutoData] + public void HaveDirectoryMatching_WithMatchingDirectory_ShouldNotThrow( + string directoryName, + string subdirectoryName) + { + MockFileSystem fileSystem = new(); + fileSystem.Initialize() + .WithSubdirectory(directoryName).Initialized(d => d + .WithSubdirectory(subdirectoryName)); + IDirectoryInfo sut = fileSystem.DirectoryInfo.New(directoryName); + + sut.Should().HaveDirectoryMatching(subdirectoryName); + } + + [Theory] + [AutoData] + public void HaveDirectoryMatching_WithoutMatchingDirectory_ShouldThrow( + string directoryName, + string subdirectoryName, + string because) + { + MockFileSystem fileSystem = new(); + fileSystem.Initialize() + .WithSubdirectory(directoryName).Initialized(d => d + .WithSubdirectory("not-matching-directory")); + IDirectoryInfo sut = fileSystem.DirectoryInfo.New(directoryName); + + Exception? exception = Record.Exception(() => + { + sut.Should().HaveDirectoryMatching(subdirectoryName, because); + }); + + exception.Should().NotBeNull(); + exception!.Message.Should() + .Be( + $"Expected directory \"{directoryName}\" to contain at least one directory matching \"{subdirectoryName}\" {because}, but none was found."); + } + [Theory] [InlineAutoData(null)] [InlineAutoData("")] @@ -85,6 +161,106 @@ public void HaveFileMatching_WithoutMatchingFile_ShouldThrow( $"Expected directory \"{directoryName}\" to contain at least one file matching \"{fileName}\" {because}, but none was found."); } + [Theory] + [InlineAutoData(null)] + [InlineAutoData("")] + public void HaveSingleDirectory_InvalidDirectoryName_ShouldThrow(string? invalidDirectoryName, + string because) + { + MockFileSystem fileSystem = new(); + fileSystem.Initialize() + .WithSubdirectory("foo"); + IDirectoryInfo sut = fileSystem.DirectoryInfo.New("foo"); + + Exception? exception = Record.Exception(() => + { + sut.Should().HaveSingleDirectory(invalidDirectoryName!, because); + }); + + exception.Should().NotBeNull(); + exception!.Message.Should().NotBeNullOrEmpty(); + exception.Message.Should().NotContain(because); + } + + [Theory] + [AutoData] + public void HaveSingleDirectory_Null_ShouldThrow(string because) + { + IDirectoryInfo? sut = null; + + Exception? exception = Record.Exception(() => + { + sut.Should().HaveSingleDirectory(because: because); + }); + + exception.Should().NotBeNull(); + exception!.Message.Should().Contain("null"); + exception.Message.Should().NotContain(because); + } + + [Theory] + [AutoData] + public void HaveSingleDirectory_WithMatchingDirectory_ShouldNotThrow( + string directoryName, + string subdirectoryName) + { + MockFileSystem fileSystem = new(); + fileSystem.Initialize() + .WithSubdirectory(directoryName).Initialized(d => d + .WithSubdirectory(subdirectoryName)); + IDirectoryInfo sut = fileSystem.DirectoryInfo.New(directoryName); + + sut.Should().HaveSingleDirectory(subdirectoryName); + } + + [Theory] + [AutoData] + public void HaveSingleDirectory_WithMultipleMatchingDirectory_ShouldThrow( + string directoryName, + string because) + { + MockFileSystem fileSystem = new(); + fileSystem.Initialize() + .WithSubdirectory(directoryName).Initialized(d => d + .WithSubdirectory("directory1.txt") + .WithSubdirectory("directory2.txt")); + IDirectoryInfo sut = fileSystem.DirectoryInfo.New(directoryName); + + Exception? exception = Record.Exception(() => + { + sut.Should().HaveSingleDirectory("directory*.txt", because); + }); + + exception.Should().NotBeNull(); + exception!.Message.Should() + .Be( + $"Expected directory \"{directoryName}\" to contain exactly one directory matching \"directory*.txt\" {because}, but found 2."); + } + + [Theory] + [AutoData] + public void HaveSingleDirectory_WithoutMatchingDirectory_ShouldThrow( + string directoryName, + string subdirectoryName, + string because) + { + MockFileSystem fileSystem = new(); + fileSystem.Initialize() + .WithSubdirectory(directoryName).Initialized(d => d + .WithSubdirectory("not-matching-directory")); + IDirectoryInfo sut = fileSystem.DirectoryInfo.New(directoryName); + + Exception? exception = Record.Exception(() => + { + sut.Should().HaveSingleDirectory(subdirectoryName, because); + }); + + exception.Should().NotBeNull(); + exception!.Message.Should() + .Be( + $"Expected directory \"{directoryName}\" to contain exactly one directory matching \"{subdirectoryName}\" {because}, but found 0."); + } + [Theory] [InlineAutoData(null)] [InlineAutoData("")]