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 @@
+
+[](https://www.nuget.org/packages/Testably.Abstractions.Compression)
+[](https://github.com/Testably/Testably.Abstractions/actions/workflows/build.yml)
+[](https://sonarcloud.io/summary/overall?id=Testably_Testably.Abstractions&branch=main)
+[](https://sonarcloud.io/summary/overall?id=Testably_Testably.Abstractions&branch=main)
+[](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 @@
+