diff --git a/.github/stryker/Stryker.Config.FluentAssertions.json b/.github/stryker/Stryker.Config.FluentAssertions.json new file mode 100644 index 000000000..c7610ddb1 --- /dev/null +++ b/.github/stryker/Stryker.Config.FluentAssertions.json @@ -0,0 +1,24 @@ +{ + "stryker-config": { + "project-info": { + "name": "github.com/Testably/Testably.Abstractions", + "module": "Testably.Abstractions.FluentAssertions" + }, + "test-projects": [ + "./Testably.Abstractions.FluentAssertions.Tests/Testably.Abstractions.FluentAssertions.Tests.csproj" + ], + "project": "Testably.Abstractions.FluentAssertions.csproj", + "target-framework": "net7.0", + "since": { + "ignore-changes-in": [ + "**/.github/**/*.*" + ] + }, + "reporters": [ + "html", + "progress", + "cleartext" + ], + "mutation-level": "Advanced" + } +} diff --git a/.github/workflows/ci-stryker.yml b/.github/workflows/ci-stryker.yml index 2e5276667..3385ea904 100644 --- a/.github/workflows/ci-stryker.yml +++ b/.github/workflows/ci-stryker.yml @@ -51,6 +51,14 @@ jobs: cd Tests ../../tools/dotnet-stryker -f ../.github/stryker/Stryker.Config.Compression.json -v "${GITHUB_HEAD_REF}" -r "html" -r "cleartext" --since:main mv ./StrykerOutput/**/reports/*.html ./StrykerOutput/Reports/Testably.Abstractions.Compression-report.html + - name: Analyze Testably.Abstractions.FluentAssertions + env: + STRYKER_DASHBOARD_API_KEY: ${{ secrets.STRYKER_DASHBOARD_API_KEY }} + shell: bash + run: | + cd Tests + ../../tools/dotnet-stryker -f ../.github/stryker/Stryker.Config.FluentAssertions.json -v "${GITHUB_HEAD_REF}" -r "html" -r "cleartext" --since:main + mv ./StrykerOutput/**/reports/*.html ./StrykerOutput/Reports/Testably.Abstractions.FluentAssertions-report.html - name: Analyze Testably.Abstractions.Testing env: STRYKER_DASHBOARD_API_KEY: ${{ secrets.STRYKER_DASHBOARD_API_KEY }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 5d866b1e2..97d202aa7 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -267,6 +267,13 @@ jobs: run: | cd Tests ../../tools/dotnet-stryker -f ../.github/stryker/Stryker.Config.Compression.json -v "${GITHUB_REF#refs/heads/}" -r "Dashboard" -r "cleartext" + - name: Analyze Testably.Abstractions.FluentAssertions + env: + STRYKER_DASHBOARD_API_KEY: ${{ secrets.STRYKER_DASHBOARD_API_KEY }} + shell: bash + run: | + cd Tests + ../../tools/dotnet-stryker -f ../.github/stryker/Stryker.Config.FluentAssertions.json -v "${GITHUB_REF#refs/heads/}" -r "Dashboard" -r "cleartext" deploy: name: Deploy @@ -298,7 +305,7 @@ jobs: version="${GITHUB_REF#refs/heads/release/}" # Add changelog badge to README.md sed -i -e "2 a\[!\[Changelog](https:\/\/img\.shields\.io\/badge\/Changelog-${version}-blue)](https:\/\/github\.com\/Testably\/Testably\.Abstractions\/releases\/tag\/${version})" "./README.md" - for f in "README.md" "Docs/AccessControl.md" "Docs/Compression.md" "Docs/Interface.md" "Docs/Testing.md" + for f in "README.md" "Docs/AccessControl.md" "Docs/Compression.md" "Docs/FluentAssertions.md" "Docs/Interface.md" "Docs/Testing.md" do echo "Processing $f" # always double quote "$f" filename # do something on $f diff --git a/.github/workflows/stryker.yml b/.github/workflows/stryker.yml index c60b81167..1adb00b62 100644 --- a/.github/workflows/stryker.yml +++ b/.github/workflows/stryker.yml @@ -73,3 +73,10 @@ jobs: run: | cd Tests ../../tools/dotnet-stryker -f ../.github/stryker/Stryker.Config.Compression.json -v "${GITHUB_REF#refs/heads/}" -r "Dashboard" -r "cleartext" + - name: Analyze Testably.Abstractions.FluentAssertions + env: + STRYKER_DASHBOARD_API_KEY: ${{ secrets.STRYKER_DASHBOARD_API_KEY }} + shell: bash + run: | + cd Tests + ../../tools/dotnet-stryker -f ../.github/stryker/Stryker.Config.FluentAssertions.json -v "${GITHUB_REF#refs/heads/}" -r "Dashboard" -r "cleartext" diff --git a/Directory.Packages.props b/Directory.Packages.props index b1c6f1fe9..ca4c211e9 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -8,6 +8,7 @@ + diff --git a/Docs/FluentAssertions.md b/Docs/FluentAssertions.md new file mode 100644 index 000000000..ca5d24c0c --- /dev/null +++ b/Docs/FluentAssertions.md @@ -0,0 +1,8 @@ +![Testably.Abstractions](https://raw.githubusercontent.com/Testably/Testably.Abstractions/main/Docs/Images/social-preview.png) +[![Nuget](https://img.shields.io/nuget/v/Testably.Abstractions.Compression)](https://www.nuget.org/packages/Testably.Abstractions.Compression) +[![Build](https://github.com/Testably/Testably.Abstractions/actions/workflows/build.yml/badge.svg)](https://github.com/Testably/Testably.Abstractions/actions/workflows/build.yml) +[![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=Testably_Testably.Abstractions&branch=main&metric=alert_status)](https://sonarcloud.io/summary/overall?id=Testably_Testably.Abstractions&branch=main) +[![Coverage](https://sonarcloud.io/api/project_badges/measure?project=Testably_Testably.Abstractions&branch=main&metric=coverage)](https://sonarcloud.io/summary/overall?id=Testably_Testably.Abstractions&branch=main) +[![Mutation testing badge](https://img.shields.io/endpoint?style=flat&url=https%3A%2F%2Fbadge-api.stryker-mutator.io%2Fgithub.com%2FTestably%2FTestably.Abstractions%2Fmain)](https://dashboard.stryker-mutator.io/reports/github.com/Testably/Testably.Abstractions/main) + +FluentAssertions extensions for [Testably.Abstractions](../README.md). \ No newline at end of file diff --git a/Source/Testably.Abstractions.FluentAssertions/DirectoryInfoAssertions.cs b/Source/Testably.Abstractions.FluentAssertions/DirectoryInfoAssertions.cs new file mode 100644 index 000000000..3f46658eb --- /dev/null +++ b/Source/Testably.Abstractions.FluentAssertions/DirectoryInfoAssertions.cs @@ -0,0 +1,37 @@ +namespace Testably.Abstractions.FluentAssertions; + +/// +/// Assertions on . +/// +public class DirectoryInfoAssertions : + ReferenceTypeAssertions +{ + /// + protected override string Identifier => "directory"; + + internal DirectoryInfoAssertions(IDirectoryInfo instance) + : base(instance) + { + } + + /// + /// Asserts that the current directory has at least one file which matches the . + /// + public AndConstraint HasFile( + string searchPattern = "*", string because = "", params object[] becauseArgs) + { + Execute.Assertion + .BecauseOf(because, becauseArgs) + .ForCondition(!string.IsNullOrEmpty(searchPattern)) + .FailWith( + "You can't assert a file exist in the directory if you don't pass a proper name") + .Then + .Given(() => Subject.GetFiles(searchPattern)) + .ForCondition(fileInfos => fileInfos.Length > 0) + .FailWith( + "Expected {context:directory} '{1}' to contain at least one file matching {0}{reason}, but none was found.", + _ => searchPattern, _ => Subject.Name); + + return new AndConstraint(this); + } +} diff --git a/Source/Testably.Abstractions.FluentAssertions/FileInfoAssertions.cs b/Source/Testably.Abstractions.FluentAssertions/FileInfoAssertions.cs new file mode 100644 index 000000000..779684269 --- /dev/null +++ b/Source/Testably.Abstractions.FluentAssertions/FileInfoAssertions.cs @@ -0,0 +1,50 @@ +namespace Testably.Abstractions.FluentAssertions; + +/// +/// Assertions on . +/// +public class FileInfoAssertions : + ReferenceTypeAssertions +{ + /// + protected override string Identifier => "file"; + + internal FileInfoAssertions(IFileInfo instance) + : base(instance) + { + } + + /// + /// Asserts that the current file is not read-only. + /// + public AndConstraint IsNotReadOnly( + string because = "", params object[] becauseArgs) + { + Execute.Assertion + .BecauseOf(because, becauseArgs) + .Given(() => Subject) + .ForCondition(fileInfo => !fileInfo.IsReadOnly) + .FailWith( + "Expected {context:file} '{0}' not to be read-only {reason}, but it was.", + _ => Subject.Name); + + return new AndConstraint(this); + } + + /// + /// Asserts that the current file is read-only. + /// + public AndConstraint IsReadOnly( + string because = "", params object[] becauseArgs) + { + Execute.Assertion + .BecauseOf(because, becauseArgs) + .Given(() => Subject) + .ForCondition(fileInfo => fileInfo.IsReadOnly) + .FailWith( + "Expected {context:file} '{0}' to be read-only {reason}, but it was not.", + _ => Subject.Name); + + return new AndConstraint(this); + } +} diff --git a/Source/Testably.Abstractions.FluentAssertions/FileSystemAssertions.cs b/Source/Testably.Abstractions.FluentAssertions/FileSystemAssertions.cs new file mode 100644 index 000000000..9234ff0e8 --- /dev/null +++ b/Source/Testably.Abstractions.FluentAssertions/FileSystemAssertions.cs @@ -0,0 +1,58 @@ +namespace Testably.Abstractions.FluentAssertions; + +/// +/// Assertions on . +/// +public class FileSystemAssertions : + ReferenceTypeAssertions +{ + /// + protected override string Identifier => "filesystem"; + + internal FileSystemAssertions(IFileSystem instance) + : base(instance) + { + } + + /// + /// Asserts that a directory at exists in the file system. + /// + public AndWhichConstraint HaveDirectory( + string path, string because = "", params object[] becauseArgs) + { + Execute.Assertion + .BecauseOf(because, becauseArgs) + .ForCondition(!string.IsNullOrEmpty(path)) + .FailWith("You can't assert a directory exist if you don't pass a proper name") + .Then + .Given(() => Subject.DirectoryInfo.New(path)) + .ForCondition(directoryInfo => directoryInfo.Exists) + .FailWith( + "Expected {context:filesystem} to contain directory {0}{reason}, but it did not exist.", + _ => path, directoryInfo => directoryInfo.Name); + + return new AndWhichConstraint(this, + new DirectoryInfoAssertions(Subject.DirectoryInfo.New(path))); + } + + /// + /// Asserts that a file at exists in the file system. + /// + public AndWhichConstraint HaveFile( + string path, string because = "", params object[] becauseArgs) + { + Execute.Assertion + .BecauseOf(because, becauseArgs) + .ForCondition(!string.IsNullOrEmpty(path)) + .FailWith("You can't assert a file exist if you don't pass a proper name") + .Then + .Given(() => Subject.FileInfo.New(path)) + .ForCondition(fileInfo => fileInfo.Exists) + .FailWith( + "Expected {context:filesystem} to contain file {0}{reason}, but it did not exist.", + _ => path, fileInfo => fileInfo.Name); + + return new AndWhichConstraint(this, + new FileInfoAssertions(Subject.FileInfo.New(path))); + } +} diff --git a/Source/Testably.Abstractions.FluentAssertions/FileSystemExtensions.cs b/Source/Testably.Abstractions.FluentAssertions/FileSystemExtensions.cs new file mode 100644 index 000000000..62f4a1dae --- /dev/null +++ b/Source/Testably.Abstractions.FluentAssertions/FileSystemExtensions.cs @@ -0,0 +1,16 @@ +namespace Testably.Abstractions.FluentAssertions; + +/// +/// Assertion extensions on . +/// +public static class FileSystemExtensions +{ + /// + /// Returns a object that can be used to + /// assert the current . + /// + public static FileSystemAssertions Should(this IFileSystem instance) + { + return new FileSystemAssertions(instance); + } +} diff --git a/Source/Testably.Abstractions.FluentAssertions/Testably.Abstractions.FluentAssertions.csproj b/Source/Testably.Abstractions.FluentAssertions/Testably.Abstractions.FluentAssertions.csproj new file mode 100644 index 000000000..377f5068e --- /dev/null +++ b/Source/Testably.Abstractions.FluentAssertions/Testably.Abstractions.FluentAssertions.csproj @@ -0,0 +1,23 @@ + + + + Testably.Abstractions.FluentAssertions + FluentAssertions extension methods for `Testably.Abstractions`. + Docs/FluentAssertions.md + + + + + + + + + + + + + + + diff --git a/Source/Testably.Abstractions.FluentAssertions/Usings.cs b/Source/Testably.Abstractions.FluentAssertions/Usings.cs new file mode 100644 index 000000000..868bcfa76 --- /dev/null +++ b/Source/Testably.Abstractions.FluentAssertions/Usings.cs @@ -0,0 +1,4 @@ +global using FluentAssertions; +global using FluentAssertions.Execution; +global using FluentAssertions.Primitives; +global using System.IO.Abstractions; diff --git a/Testably.Abstractions.sln b/Testably.Abstractions.sln index e3880f4cf..80bc243d1 100644 --- a/Testably.Abstractions.sln +++ b/Testably.Abstractions.sln @@ -10,8 +10,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "_", "_", "{94F99274-3518-45 .gitignore = .gitignore CODE_OF_CONDUCT.md = CODE_OF_CONDUCT.md CONTRIBUTING.md = CONTRIBUTING.md - Directory.Packages.props = Directory.Packages.props Directory.Build.props = Directory.Build.props + Directory.Packages.props = Directory.Packages.props Feature.Flags.props = Feature.Flags.props LICENSE = LICENSE nuget.config = nuget.config @@ -46,6 +46,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "stryker", "stryker", "{4D8D ProjectSection(SolutionItems) = preProject .github\stryker\Stryker.Config.AccessControl.json = .github\stryker\Stryker.Config.AccessControl.json .github\stryker\Stryker.Config.Compression.json = .github\stryker\Stryker.Config.Compression.json + .github\stryker\Stryker.Config.FluentAssertions.json = .github\stryker\Stryker.Config.FluentAssertions.json .github\stryker\Stryker.Config.json = .github\stryker\Stryker.Config.json .github\stryker\Stryker.Config.Testing.json = .github\stryker\Stryker.Config.Testing.json EndProjectSection @@ -74,6 +75,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Docs", "Docs", "{4D70CF83-2 ProjectSection(SolutionItems) = preProject Docs\AccessControl.md = Docs\AccessControl.md Docs\Compression.md = Docs\Compression.md + Docs\FluentAssertions.md = Docs\FluentAssertions.md Docs\Interface.md = Docs\Interface.md Docs\Testing.md = Docs\Testing.md EndProjectSection @@ -84,6 +86,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Testably.Abstractions.TestH EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Testably.Abstractions.Tests.SourceGenerator", "Tests\Helpers\Testably.Abstractions.Tests.SourceGenerator\Testably.Abstractions.Tests.SourceGenerator.csproj", "{97A2540A-7FB4-4D8B-B003-3F8CAA9CED87}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Testably.Abstractions.FluentAssertions", "Source\Testably.Abstractions.FluentAssertions\Testably.Abstractions.FluentAssertions.csproj", "{BC318ADF-0A23-4D0F-8FBE-4E73F839450A}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Testably.Abstractions.FluentAssertions.Tests", "Tests\Testably.Abstractions.FluentAssertions.Tests\Testably.Abstractions.FluentAssertions.Tests.csproj", "{67F7AF4E-B807-4627-9452-E5BC50E32B90}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -138,6 +144,14 @@ Global {97A2540A-7FB4-4D8B-B003-3F8CAA9CED87}.Debug|Any CPU.Build.0 = Debug|Any CPU {97A2540A-7FB4-4D8B-B003-3F8CAA9CED87}.Release|Any CPU.ActiveCfg = Release|Any CPU {97A2540A-7FB4-4D8B-B003-3F8CAA9CED87}.Release|Any CPU.Build.0 = Release|Any CPU + {BC318ADF-0A23-4D0F-8FBE-4E73F839450A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {BC318ADF-0A23-4D0F-8FBE-4E73F839450A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {BC318ADF-0A23-4D0F-8FBE-4E73F839450A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {BC318ADF-0A23-4D0F-8FBE-4E73F839450A}.Release|Any CPU.Build.0 = Release|Any CPU + {67F7AF4E-B807-4627-9452-E5BC50E32B90}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {67F7AF4E-B807-4627-9452-E5BC50E32B90}.Debug|Any CPU.Build.0 = Debug|Any CPU + {67F7AF4E-B807-4627-9452-E5BC50E32B90}.Release|Any CPU.ActiveCfg = Release|Any CPU + {67F7AF4E-B807-4627-9452-E5BC50E32B90}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -157,6 +171,7 @@ Global {B6C45D8A-A545-402E-A6B0-47BC7D9BBCF5} = {61F335A6-9CE0-4040-A34F-E70B1A55077D} {425BA8BA-7795-4749-BFC1-6FA853EBC6B7} = {B6C45D8A-A545-402E-A6B0-47BC7D9BBCF5} {97A2540A-7FB4-4D8B-B003-3F8CAA9CED87} = {B6C45D8A-A545-402E-A6B0-47BC7D9BBCF5} + {67F7AF4E-B807-4627-9452-E5BC50E32B90} = {61F335A6-9CE0-4040-A34F-E70B1A55077D} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {EC4D8481-B9FD-41B5-832A-369210993DF4} diff --git a/Tests/Helpers/Testably.Abstractions.Tests.SourceGenerator/ClassGenerators/FileSystemClassGenerator.cs b/Tests/Helpers/Testably.Abstractions.Tests.SourceGenerator/ClassGenerators/FileSystemClassGenerator.cs index 1f0c6feb5..e13b4eb94 100644 --- a/Tests/Helpers/Testably.Abstractions.Tests.SourceGenerator/ClassGenerators/FileSystemClassGenerator.cs +++ b/Tests/Helpers/Testably.Abstractions.Tests.SourceGenerator/ClassGenerators/FileSystemClassGenerator.cs @@ -15,6 +15,7 @@ protected override void GenerateSource(StringBuilder sourceBuilder, ClassModel @ => sourceBuilder.Append(@$" using Testably.Abstractions.Testing.FileSystemInitializer; using Testably.Abstractions.TestHelpers; +using System.IO.Abstractions.TestingHelpers; using Xunit.Abstractions; namespace {@class.Namespace} @@ -31,18 +32,18 @@ public abstract partial class {@class.Name} namespace {@class.Namespace}.{@class.Name} {{ // ReSharper disable once UnusedMember.Global - public sealed class MockFileSystemTests : {@class.Name}, IDisposable + public sealed class MockFileSystemTests : {@class.Name}, IDisposable {{ /// public override string BasePath => _directoryCleaner.BasePath; private readonly IDirectoryCleaner _directoryCleaner; - public MockFileSystemTests() : this(new MockFileSystem()) + public MockFileSystemTests() : this(new Testably.Abstractions.Testing.MockFileSystem()) {{ }} - private MockFileSystemTests(MockFileSystem mockFileSystem) : base( + private MockFileSystemTests(Testably.Abstractions.Testing.MockFileSystem mockFileSystem) : base( mockFileSystem, mockFileSystem.TimeSystem) {{ @@ -54,6 +55,31 @@ private MockFileSystemTests(MockFileSystem mockFileSystem) : base( public void Dispose() => _directoryCleaner.Dispose(); }} + + // ReSharper disable once UnusedMember.Global + public sealed class TestableIoMockFileSystemTests : {@class.Name}, IDisposable + {{ + /// + public override string BasePath => _directoryCleaner.BasePath; + + private readonly IDirectoryCleaner _directoryCleaner; + + public TestableIoMockFileSystemTests() : this(new System.IO.Abstractions.TestingHelpers.MockFileSystem()) + {{ + }} + + private TestableIoMockFileSystemTests(System.IO.Abstractions.TestingHelpers.MockFileSystem mockFileSystem) : base( + mockFileSystem, + new RealTimeSystem()) + {{ + _directoryCleaner = FileSystem + .SetCurrentDirectoryToEmptyTemporaryDirectory(); + }} + + /// + public void Dispose() + => _directoryCleaner.Dispose(); + }} }} #if !DEBUG || ENABLE_REALFILESYSTEMTESTS_IN_DEBUG diff --git a/Tests/Testably.Abstractions.AccessControl.Tests/Testably.Abstractions.AccessControl.Tests.csproj b/Tests/Testably.Abstractions.AccessControl.Tests/Testably.Abstractions.AccessControl.Tests.csproj index de9aba0c6..5313d34f3 100644 --- a/Tests/Testably.Abstractions.AccessControl.Tests/Testably.Abstractions.AccessControl.Tests.csproj +++ b/Tests/Testably.Abstractions.AccessControl.Tests/Testably.Abstractions.AccessControl.Tests.csproj @@ -5,6 +5,7 @@ + diff --git a/Tests/Testably.Abstractions.Compression.Tests/Testably.Abstractions.Compression.Tests.csproj b/Tests/Testably.Abstractions.Compression.Tests/Testably.Abstractions.Compression.Tests.csproj index 2fcc4e1be..99a0b53ea 100644 --- a/Tests/Testably.Abstractions.Compression.Tests/Testably.Abstractions.Compression.Tests.csproj +++ b/Tests/Testably.Abstractions.Compression.Tests/Testably.Abstractions.Compression.Tests.csproj @@ -5,6 +5,7 @@ + diff --git a/Tests/Testably.Abstractions.FluentAssertions.Tests/DirectoryInfoAssertionsTests.cs b/Tests/Testably.Abstractions.FluentAssertions.Tests/DirectoryInfoAssertionsTests.cs new file mode 100644 index 000000000..50b918b44 --- /dev/null +++ b/Tests/Testably.Abstractions.FluentAssertions.Tests/DirectoryInfoAssertionsTests.cs @@ -0,0 +1,47 @@ +using AutoFixture.Xunit2; +using FluentAssertions; +using System; +using Testably.Abstractions.Testing; +using Xunit; + +namespace Testably.Abstractions.FluentAssertions.Tests; + +public class DirectoryInfoAssertionsTests +{ + [Theory] + [AutoData] + public void HasFile_WithFile_ShouldNotThrow( + string directoryName, + string fileName) + { + MockFileSystem fileSystem = new(); + fileSystem.Initialize() + .WithSubdirectory(directoryName).Initialized(d => d + .WithFile(fileName)); + DirectoryInfoAssertions? sut = fileSystem.Should().HaveDirectory(directoryName).Which; + + sut.HasFile(fileName); + } + + [Theory] + [AutoData] + public void HasFile_WithoutFile_ShouldThrow( + string directoryName, + string fileName, + string because) + { + MockFileSystem fileSystem = new(); + fileSystem.Initialize() + .WithSubdirectory(directoryName); + DirectoryInfoAssertions? sut = fileSystem.Should().HaveDirectory(directoryName).Which; + + Exception? exception = Record.Exception(() => + { + sut.HasFile(fileName, because); + }); + + exception.Should().NotBeNull(); + exception!.Message.Should().Contain(fileName); + exception.Message.Should().Contain(because); + } +} diff --git a/Tests/Testably.Abstractions.FluentAssertions.Tests/FileInfoAssertionsTests.cs b/Tests/Testably.Abstractions.FluentAssertions.Tests/FileInfoAssertionsTests.cs new file mode 100644 index 000000000..9265a9011 --- /dev/null +++ b/Tests/Testably.Abstractions.FluentAssertions.Tests/FileInfoAssertionsTests.cs @@ -0,0 +1,81 @@ +using AutoFixture.Xunit2; +using FluentAssertions; +using System; +using Testably.Abstractions.Testing; +using Testably.Abstractions.Testing.FileSystemInitializer; +using Xunit; + +namespace Testably.Abstractions.FluentAssertions.Tests; + +public class FileInfoAssertionsTests +{ + [Theory] + [AutoData] + public void IsNotReadOnly_WithReadOnlyFile_ShouldThrow( + FileDescription fileDescription, + string because) + { + fileDescription.IsReadOnly = true; + MockFileSystem fileSystem = new(); + fileSystem.Initialize() + .With(fileDescription); + FileInfoAssertions? sut = fileSystem.Should().HaveFile(fileDescription.Name).Which; + + Exception? exception = Record.Exception(() => + { + sut.IsNotReadOnly(because); + }); + + exception.Should().NotBeNull(); + exception!.Message.Should().Contain(fileDescription.Name); + exception.Message.Should().Contain(because); + } + + [Theory] + [AutoData] + public void IsNotReadOnly_WithWritableFile_ShouldNotThrow(FileDescription fileDescription) + { + fileDescription.IsReadOnly = false; + MockFileSystem fileSystem = new(); + fileSystem.Initialize() + .With(fileDescription); + FileInfoAssertions? sut = fileSystem.Should().HaveFile(fileDescription.Name).Which; + + sut.IsNotReadOnly(); + } + + [Theory] + [AutoData] + public void IsReadOnly_WithReadOnlyFile_ShouldNotThrow(FileDescription fileDescription) + { + fileDescription.IsReadOnly = true; + MockFileSystem fileSystem = new(); + fileSystem.Initialize() + .With(fileDescription); + FileInfoAssertions? sut = fileSystem.Should().HaveFile(fileDescription.Name).Which; + + sut.IsReadOnly(); + } + + [Theory] + [AutoData] + public void IsReadOnly_WithWritableFile_ShouldThrow( + FileDescription fileDescription, + string because) + { + fileDescription.IsReadOnly = false; + MockFileSystem fileSystem = new(); + fileSystem.Initialize() + .With(fileDescription); + FileInfoAssertions? sut = fileSystem.Should().HaveFile(fileDescription.Name).Which; + + Exception? exception = Record.Exception(() => + { + sut.IsReadOnly(because); + }); + + exception.Should().NotBeNull(); + exception!.Message.Should().Contain(fileDescription.Name); + exception.Message.Should().Contain(because); + } +} diff --git a/Tests/Testably.Abstractions.FluentAssertions.Tests/FileSystemAssertionsTests.cs b/Tests/Testably.Abstractions.FluentAssertions.Tests/FileSystemAssertionsTests.cs new file mode 100644 index 000000000..4ecd0da3a --- /dev/null +++ b/Tests/Testably.Abstractions.FluentAssertions.Tests/FileSystemAssertionsTests.cs @@ -0,0 +1,90 @@ +using AutoFixture.Xunit2; +using FluentAssertions; +using System; +using Testably.Abstractions.Testing; +using Xunit; + +namespace Testably.Abstractions.FluentAssertions.Tests; + +public class FileSystemAssertionsTests +{ + [Theory] + [AutoData] + public void HaveDirectory_WithDirectory_ShouldNotThrow(string path) + { + MockFileSystem fileSystem = new(); + fileSystem.Initialize() + .WithSubdirectory(path); + + fileSystem.Should().HaveDirectory(path); + } + + [Theory] + [AutoData] + public void HaveDirectory_WithMultipleDirectories_ShouldNotThrow(string path1, string path2) + { + MockFileSystem fileSystem = new(); + fileSystem.Initialize() + .WithSubdirectory(path1) + .WithSubdirectory(path2); + + fileSystem.Should().HaveDirectory(path1).And.HaveDirectory(path2); + } + + [Theory] + [AutoData] + public void HaveDirectory_WithoutDirectory_ShouldThrow( + string path, + string because) + { + MockFileSystem fileSystem = new(); + + Exception? exception = Record.Exception(() => + { + fileSystem.Should().HaveDirectory(path, because); + }); + + exception.Should().NotBeNull(); + exception!.Message.Should().Contain(path); + exception.Message.Should().Contain(because); + } + + [Theory] + [AutoData] + public void HaveFile_WithFile_ShouldNotThrow(string path) + { + MockFileSystem fileSystem = new(); + fileSystem.Initialize() + .WithFile(path); + + fileSystem.Should().HaveFile(path); + } + + [Theory] + [AutoData] + public void HaveFile_WithMultipleDirectories_ShouldNotThrow(string path1, string path2) + { + MockFileSystem fileSystem = new(); + fileSystem.Initialize() + .WithFile(path1) + .WithFile(path2); + + fileSystem.Should().HaveFile(path1).And.HaveFile(path2); + } + + [Theory] + [AutoData] + public void HaveFile_WithoutFile_ShouldThrow(string path, string because) + { + MockFileSystem fileSystem = new(); + + Exception? exception = Record.Exception(() => + { + fileSystem.Should().HaveFile(path, because); + }); + + exception.Should().NotBeNull(); + exception!.Message.Should().Contain(path); + exception.Message.Should().Contain(because); + } +} diff --git a/Tests/Testably.Abstractions.FluentAssertions.Tests/Testably.Abstractions.FluentAssertions.Tests.csproj b/Tests/Testably.Abstractions.FluentAssertions.Tests/Testably.Abstractions.FluentAssertions.Tests.csproj new file mode 100644 index 000000000..42301a073 --- /dev/null +++ b/Tests/Testably.Abstractions.FluentAssertions.Tests/Testably.Abstractions.FluentAssertions.Tests.csproj @@ -0,0 +1,13 @@ + + + + net6.0 + + + + + + + + diff --git a/Tests/Testably.Abstractions.Tests/Internal/DirectoryInfoWrapperTests.cs b/Tests/Testably.Abstractions.Tests/Internal/DirectoryInfoWrapperTests.cs index 09c05b15c..0bcfef568 100644 --- a/Tests/Testably.Abstractions.Tests/Internal/DirectoryInfoWrapperTests.cs +++ b/Tests/Testably.Abstractions.Tests/Internal/DirectoryInfoWrapperTests.cs @@ -1,4 +1,5 @@ using System.IO; +using DirectoryInfoWrapper = Testably.Abstractions.FileSystem.DirectoryInfoWrapper; namespace Testably.Abstractions.Tests.Internal; diff --git a/Tests/Testably.Abstractions.Tests/Internal/DriveInfoWrapperTests.cs b/Tests/Testably.Abstractions.Tests/Internal/DriveInfoWrapperTests.cs index b88f7a0a4..89841c198 100644 --- a/Tests/Testably.Abstractions.Tests/Internal/DriveInfoWrapperTests.cs +++ b/Tests/Testably.Abstractions.Tests/Internal/DriveInfoWrapperTests.cs @@ -1,4 +1,6 @@ -namespace Testably.Abstractions.Tests.Internal; +using DriveInfoWrapper = Testably.Abstractions.FileSystem.DriveInfoWrapper; + +namespace Testably.Abstractions.Tests.Internal; public class DriveInfoWrapperTests { diff --git a/Tests/Testably.Abstractions.Tests/Internal/FileInfoWrapperTests.cs b/Tests/Testably.Abstractions.Tests/Internal/FileInfoWrapperTests.cs index 999266a37..69083e95f 100644 --- a/Tests/Testably.Abstractions.Tests/Internal/FileInfoWrapperTests.cs +++ b/Tests/Testably.Abstractions.Tests/Internal/FileInfoWrapperTests.cs @@ -1,4 +1,5 @@ using System.IO; +using FileInfoWrapper = Testably.Abstractions.FileSystem.FileInfoWrapper; namespace Testably.Abstractions.Tests.Internal; diff --git a/Tests/Testably.Abstractions.Tests/Internal/FileSystemInfoWrapperTests.cs b/Tests/Testably.Abstractions.Tests/Internal/FileSystemInfoWrapperTests.cs index 05a3781d4..5e9f02c15 100644 --- a/Tests/Testably.Abstractions.Tests/Internal/FileSystemInfoWrapperTests.cs +++ b/Tests/Testably.Abstractions.Tests/Internal/FileSystemInfoWrapperTests.cs @@ -1,4 +1,6 @@ using System.IO; +using DirectoryInfoWrapper = Testably.Abstractions.FileSystem.DirectoryInfoWrapper; +using FileInfoWrapper = Testably.Abstractions.FileSystem.FileInfoWrapper; namespace Testably.Abstractions.Tests.Internal; diff --git a/Tests/Testably.Abstractions.Tests/Internal/FileSystemWatcherWrapperTests.cs b/Tests/Testably.Abstractions.Tests/Internal/FileSystemWatcherWrapperTests.cs index 88021a4a9..25059c6cd 100644 --- a/Tests/Testably.Abstractions.Tests/Internal/FileSystemWatcherWrapperTests.cs +++ b/Tests/Testably.Abstractions.Tests/Internal/FileSystemWatcherWrapperTests.cs @@ -1,4 +1,6 @@ -namespace Testably.Abstractions.Tests.Internal; +using FileSystemWatcherWrapper = Testably.Abstractions.FileSystem.FileSystemWatcherWrapper; + +namespace Testably.Abstractions.Tests.Internal; public class FileSystemWatcherWrapperTests { diff --git a/Tests/Testably.Abstractions.Tests/Testably.Abstractions.Tests.csproj b/Tests/Testably.Abstractions.Tests/Testably.Abstractions.Tests.csproj index d2f919918..3442deb08 100644 --- a/Tests/Testably.Abstractions.Tests/Testably.Abstractions.Tests.csproj +++ b/Tests/Testably.Abstractions.Tests/Testably.Abstractions.Tests.csproj @@ -5,6 +5,7 @@ +