From aaa7154b1e814004f1a37ae50f7b5e8b7781db19 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Valentin=20Breu=C3=9F?= Date: Mon, 21 Aug 2023 12:26:36 +0200 Subject: [PATCH 1/2] Add HasSingleFileMatching assertion --- .../DirectoryAssertions.cs | 38 ++++++- .../DirectoryInfoAssertions.cs | 9 ++ .../DirectoryAssertionsTests.cs | 85 +++++++++++++++ .../DirectoryInfoAssertionsTests.cs | 100 ++++++++++++++++++ 4 files changed, 230 insertions(+), 2 deletions(-) diff --git a/Source/Testably.Abstractions.FluentAssertions/DirectoryAssertions.cs b/Source/Testably.Abstractions.FluentAssertions/DirectoryAssertions.cs index 9577c76..c96643a 100644 --- a/Source/Testably.Abstractions.FluentAssertions/DirectoryAssertions.cs +++ b/Source/Testably.Abstractions.FluentAssertions/DirectoryAssertions.cs @@ -1,4 +1,6 @@ -namespace Testably.Abstractions.FluentAssertions; +using System.Linq; + +namespace Testably.Abstractions.FluentAssertions; /// /// Assertions on . @@ -14,6 +16,37 @@ internal DirectoryAssertions(IDirectoryInfo? instance) { } + /// + /// Asserts that the directory contains exactly one file matching the given . + /// + public AndWhichConstraint HasSingleFile( + 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 name") + .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())); + } + /// /// Asserts that the current directory has at least one file which matches the . /// @@ -47,7 +80,8 @@ public AndConstraint HasFilesMatching( => directoryInfo.GetFiles(searchPattern).Length >= minimumCount) .FailWith( $"Expected {{context}} {{1}} to contain at least {(minimumCount == 1 ? "one file" : $"{minimumCount} files")} matching {{0}}{{reason}}, but {(minimumCount == 1 ? "none was" : "only {2} were")} found.", - _ => searchPattern, directoryInfo => directoryInfo.Name, + _ => searchPattern, + directoryInfo => directoryInfo.Name, directoryInfo => directoryInfo.GetFiles(searchPattern).Length); return new AndConstraint(this); diff --git a/Source/Testably.Abstractions.FluentAssertions/DirectoryInfoAssertions.cs b/Source/Testably.Abstractions.FluentAssertions/DirectoryInfoAssertions.cs index 4d8d186..806d978 100644 --- a/Source/Testably.Abstractions.FluentAssertions/DirectoryInfoAssertions.cs +++ b/Source/Testably.Abstractions.FluentAssertions/DirectoryInfoAssertions.cs @@ -23,4 +23,13 @@ public AndConstraint HaveFileMatching( new DirectoryAssertions(Subject).HasFileMatching(searchPattern, because, becauseArgs); return new AndConstraint(this); } + + /// + /// 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).HasSingleFile(searchPattern, because, becauseArgs); + } } diff --git a/Tests/Testably.Abstractions.FluentAssertions.Tests/DirectoryAssertionsTests.cs b/Tests/Testably.Abstractions.FluentAssertions.Tests/DirectoryAssertionsTests.cs index ad620c8..50dfbf2 100644 --- a/Tests/Testably.Abstractions.FluentAssertions.Tests/DirectoryAssertionsTests.cs +++ b/Tests/Testably.Abstractions.FluentAssertions.Tests/DirectoryAssertionsTests.cs @@ -99,4 +99,89 @@ public void HasFilesMatching_WithoutTooLittleFiles_ShouldThrow( .Be( $"Expected directory \"{directoryName}\" to contain at least {expectedCount} files matching \"{fileNamePrefix}*\" {because}, but only {matchingCount} were found."); } + + [Theory] + [InlineAutoData(null)] + [InlineAutoData("")] + public void HasSingleFileMatching_InvalidFileName_ShouldThrow(string? invalidFileName, + string because) + { + MockFileSystem fileSystem = new(); + fileSystem.Initialize() + .WithSubdirectory("foo"); + DirectoryAssertions? sut = fileSystem.Should().HaveDirectory("foo").Which; + + Exception? exception = Record.Exception(() => + { + sut.HasSingleFileMatching(invalidFileName!, because); + }); + + exception.Should().NotBeNull(); + exception!.Message.Should().NotBeNullOrEmpty(); + exception.Message.Should().NotContain(because); + } + + [Theory] + [AutoData] + public void HasSingleFileMatching_WithMatchingFile_ShouldNotThrow( + string directoryName, + string fileName) + { + MockFileSystem fileSystem = new(); + fileSystem.Initialize() + .WithSubdirectory(directoryName).Initialized(d => d + .WithFile(fileName)); + DirectoryAssertions? sut = fileSystem.Should().HaveDirectory(directoryName).Which; + + sut.HasSingleFileMatching(fileName); + } + + [Theory] + [AutoData] + public void HasSingleFileMatching_WithMultipleMatchingFile_ShouldThrow( + string directoryName, + string fileName, + string because) + { + MockFileSystem fileSystem = new(); + fileSystem.Initialize() + .WithSubdirectory(directoryName).Initialized(d => d + .WithFile($"{fileName}-1.txt") + .WithFile($"{fileName}-2.txt")); + DirectoryAssertions? sut = fileSystem.Should().HaveDirectory(directoryName).Which; + + Exception? exception = Record.Exception(() => + { + sut.HasSingleFileMatching($"{fileName}*", because); + }); + + exception.Should().NotBeNull(); + exception!.Message.Should() + .Be( + $"Expected directory \"{directoryName}\" to contain exactly one file matching \"{fileName}*\" {because}, but found 2."); + } + + [Theory] + [AutoData] + public void HasSingleFileMatching_WithoutMatchingFile_ShouldThrow( + string directoryName, + string fileName, + string because) + { + MockFileSystem fileSystem = new(); + fileSystem.Initialize() + .WithSubdirectory(directoryName).Initialized(d => d + .WithFile("not-matching-file")); + DirectoryAssertions? sut = fileSystem.Should().HaveDirectory(directoryName).Which; + + Exception? exception = Record.Exception(() => + { + sut.HasSingleFileMatching(fileName, because); + }); + + exception.Should().NotBeNull(); + exception!.Message.Should() + .Be( + $"Expected directory \"{directoryName}\" to contain exactly one file matching \"{fileName}\" {because}, but found 0."); + } } diff --git a/Tests/Testably.Abstractions.FluentAssertions.Tests/DirectoryInfoAssertionsTests.cs b/Tests/Testably.Abstractions.FluentAssertions.Tests/DirectoryInfoAssertionsTests.cs index 4ee3715..f1fbf60 100644 --- a/Tests/Testably.Abstractions.FluentAssertions.Tests/DirectoryInfoAssertionsTests.cs +++ b/Tests/Testably.Abstractions.FluentAssertions.Tests/DirectoryInfoAssertionsTests.cs @@ -84,4 +84,104 @@ public void HaveFileMatching_WithoutMatchingFile_ShouldThrow( .Be( $"Expected directory \"{directoryName}\" to contain at least one file matching \"{fileName}\" {because}, but none was found."); } + + [Theory] + [InlineAutoData(null)] + [InlineAutoData("")] + public void HaveSingleFile_InvalidFileName_ShouldThrow(string? invalidFileName, + string because) + { + MockFileSystem fileSystem = new(); + fileSystem.Initialize() + .WithSubdirectory("foo"); + IDirectoryInfo sut = fileSystem.DirectoryInfo.New("foo"); + + Exception? exception = Record.Exception(() => + { + sut.Should().HaveSingleFile(invalidFileName!, because); + }); + + exception.Should().NotBeNull(); + exception!.Message.Should().NotBeNullOrEmpty(); + exception.Message.Should().NotContain(because); + } + + [Theory] + [AutoData] + public void HaveSingleFile_Null_ShouldThrow(string because) + { + IDirectoryInfo? sut = null; + + Exception? exception = Record.Exception(() => + { + sut.Should().HaveSingleFile(because: because); + }); + + exception.Should().NotBeNull(); + exception!.Message.Should().Contain("null"); + exception.Message.Should().NotContain(because); + } + + [Theory] + [AutoData] + public void HaveSingleFile_WithMatchingFile_ShouldNotThrow( + string directoryName, + string fileName) + { + MockFileSystem fileSystem = new(); + fileSystem.Initialize() + .WithSubdirectory(directoryName).Initialized(d => d + .WithFile(fileName)); + IDirectoryInfo sut = fileSystem.DirectoryInfo.New(directoryName); + + sut.Should().HaveSingleFile(fileName); + } + + [Theory] + [AutoData] + public void HaveSingleFile_WithMultipleMatchingFile_ShouldThrow( + string directoryName, + string because) + { + MockFileSystem fileSystem = new(); + fileSystem.Initialize() + .WithSubdirectory(directoryName).Initialized(d => d + .WithFile("file1.txt") + .WithFile("file2.txt")); + IDirectoryInfo sut = fileSystem.DirectoryInfo.New(directoryName); + + Exception? exception = Record.Exception(() => + { + sut.Should().HaveSingleFile("file*.txt", because); + }); + + exception.Should().NotBeNull(); + exception!.Message.Should() + .Be( + $"Expected directory \"{directoryName}\" to contain exactly one file matching \"file*.txt\" {because}, but found 2."); + } + + [Theory] + [AutoData] + public void HaveSingleFile_WithoutMatchingFile_ShouldThrow( + string directoryName, + string fileName, + string because) + { + MockFileSystem fileSystem = new(); + fileSystem.Initialize() + .WithSubdirectory(directoryName).Initialized(d => d + .WithFile("not-matching-file")); + IDirectoryInfo sut = fileSystem.DirectoryInfo.New(directoryName); + + Exception? exception = Record.Exception(() => + { + sut.Should().HaveSingleFile(fileName, because); + }); + + exception.Should().NotBeNull(); + exception!.Message.Should() + .Be( + $"Expected directory \"{directoryName}\" to contain exactly one file matching \"{fileName}\" {because}, but found 0."); + } } From 83dce5b9205200342a9fc59fe2e30f287dbc4d35 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Valentin=20Breu=C3=9F?= Date: Mon, 21 Aug 2023 12:31:00 +0200 Subject: [PATCH 2/2] Fix naming --- .../DirectoryAssertions.cs | 2 +- .../DirectoryInfoAssertions.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Source/Testably.Abstractions.FluentAssertions/DirectoryAssertions.cs b/Source/Testably.Abstractions.FluentAssertions/DirectoryAssertions.cs index c96643a..88fcc48 100644 --- a/Source/Testably.Abstractions.FluentAssertions/DirectoryAssertions.cs +++ b/Source/Testably.Abstractions.FluentAssertions/DirectoryAssertions.cs @@ -19,7 +19,7 @@ internal DirectoryAssertions(IDirectoryInfo? instance) /// /// Asserts that the directory contains exactly one file matching the given . /// - public AndWhichConstraint HasSingleFile( + public AndWhichConstraint HasSingleFileMatching( string searchPattern = "*", string because = "", params object[] becauseArgs) { Execute.Assertion diff --git a/Source/Testably.Abstractions.FluentAssertions/DirectoryInfoAssertions.cs b/Source/Testably.Abstractions.FluentAssertions/DirectoryInfoAssertions.cs index 806d978..4575f7e 100644 --- a/Source/Testably.Abstractions.FluentAssertions/DirectoryInfoAssertions.cs +++ b/Source/Testably.Abstractions.FluentAssertions/DirectoryInfoAssertions.cs @@ -30,6 +30,6 @@ public AndConstraint HaveFileMatching( public AndWhichConstraint HaveSingleFile( string searchPattern = "*", string because = "", params object[] becauseArgs) { - return new DirectoryAssertions(Subject).HasSingleFile(searchPattern, because, becauseArgs); + return new DirectoryAssertions(Subject).HasSingleFileMatching(searchPattern, because, becauseArgs); } }