From 8ed229122c13d4920fa53e1abed54ebb368d201c Mon Sep 17 00:00:00 2001 From: "Daniel Mackay [SSW]" <2636640+danielmackay@users.noreply.github.com> Date: Fri, 17 May 2024 09:55:07 +1000 Subject: [PATCH] =?UTF-8?q?=F0=9F=A7=AA=20Architecture=20tests=20to=20enfo?= =?UTF-8?q?rce=20Domain=20purity=20(#87)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- VerticalSliceArchitectureTemplate.sln | 7 ++ .../Features/Todos/Domain/Todo.cs | 2 +- .../Program.cs | 4 +- .../ArchitectureTests.cs | 69 +++++++++++++++++++ .../Common/TestResultExtensions.cs | 18 +++++ .../GlobalUsings.cs | 1 + .../PredicateListExt.cs | 11 +++ .../TestBase.cs | 28 ++++++++ ...VerticalSliceArchitecture.ArchTests.csproj | 31 +++++++++ 9 files changed, 169 insertions(+), 2 deletions(-) create mode 100644 tests/VerticalSliceArchitecture.ArchTests/ArchitectureTests.cs create mode 100644 tests/VerticalSliceArchitecture.ArchTests/Common/TestResultExtensions.cs create mode 100644 tests/VerticalSliceArchitecture.ArchTests/GlobalUsings.cs create mode 100644 tests/VerticalSliceArchitecture.ArchTests/PredicateListExt.cs create mode 100644 tests/VerticalSliceArchitecture.ArchTests/TestBase.cs create mode 100644 tests/VerticalSliceArchitecture.ArchTests/VerticalSliceArchitecture.ArchTests.csproj diff --git a/VerticalSliceArchitectureTemplate.sln b/VerticalSliceArchitectureTemplate.sln index 9616507..a7cc90c 100644 --- a/VerticalSliceArchitectureTemplate.sln +++ b/VerticalSliceArchitectureTemplate.sln @@ -16,6 +16,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{21C89A08 EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "VerticalSliceArchitectureTemplate.Unit.Tests", "tests\VerticalSliceArchitectureTemplate.Unit.Tests\VerticalSliceArchitectureTemplate.Unit.Tests.csproj", "{07A76FBE-083C-49AA-856E-0C1B95DEB7B2}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "VerticalSliceArchitecture.ArchTests", "tests\VerticalSliceArchitecture.ArchTests\VerticalSliceArchitecture.ArchTests.csproj", "{C07689FD-DF63-46DB-B5C6-93B73CD72C5E}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -30,9 +32,14 @@ Global {07A76FBE-083C-49AA-856E-0C1B95DEB7B2}.Debug|Any CPU.Build.0 = Debug|Any CPU {07A76FBE-083C-49AA-856E-0C1B95DEB7B2}.Release|Any CPU.ActiveCfg = Release|Any CPU {07A76FBE-083C-49AA-856E-0C1B95DEB7B2}.Release|Any CPU.Build.0 = Release|Any CPU + {C07689FD-DF63-46DB-B5C6-93B73CD72C5E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C07689FD-DF63-46DB-B5C6-93B73CD72C5E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C07689FD-DF63-46DB-B5C6-93B73CD72C5E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C07689FD-DF63-46DB-B5C6-93B73CD72C5E}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(NestedProjects) = preSolution {8EB413BD-C5DF-49A5-A372-4AEAF4999952} = {92C5999C-A447-479E-8630-064E6FDC78DA} {07A76FBE-083C-49AA-856E-0C1B95DEB7B2} = {21C89A08-D942-45BE-A302-4EEB543573C0} + {C07689FD-DF63-46DB-B5C6-93B73CD72C5E} = {21C89A08-D942-45BE-A302-4EEB543573C0} EndGlobalSection EndGlobal diff --git a/src/VerticalSliceArchitectureTemplate/Features/Todos/Domain/Todo.cs b/src/VerticalSliceArchitectureTemplate/Features/Todos/Domain/Todo.cs index 62f1268..bd1bc9b 100644 --- a/src/VerticalSliceArchitectureTemplate/Features/Todos/Domain/Todo.cs +++ b/src/VerticalSliceArchitectureTemplate/Features/Todos/Domain/Todo.cs @@ -29,4 +29,4 @@ public void Complete() StagedEvents.Add(new TodoCompletedEvent(Id)); } -} \ No newline at end of file +} diff --git a/src/VerticalSliceArchitectureTemplate/Program.cs b/src/VerticalSliceArchitectureTemplate/Program.cs index 4b1612a..ed5d906 100644 --- a/src/VerticalSliceArchitectureTemplate/Program.cs +++ b/src/VerticalSliceArchitectureTemplate/Program.cs @@ -36,4 +36,6 @@ app.RegisterEndpoints(appAssembly); -app.Run(); \ No newline at end of file +app.Run(); + +public partial class Program; diff --git a/tests/VerticalSliceArchitecture.ArchTests/ArchitectureTests.cs b/tests/VerticalSliceArchitecture.ArchTests/ArchitectureTests.cs new file mode 100644 index 0000000..d9f856b --- /dev/null +++ b/tests/VerticalSliceArchitecture.ArchTests/ArchitectureTests.cs @@ -0,0 +1,69 @@ +using FluentAssertions; +using VerticalSliceArchitecture.ArchTests.Common; +using Xunit.Abstractions; + +namespace VerticalSliceArchitecture.ArchTests; + +public class ArchitectureTests : TestBase +{ + private readonly ITestOutputHelper _outputHelper; + + public ArchitectureTests(ITestOutputHelper outputHelper) + { + _outputHelper = outputHelper; + } + + private readonly string[] _applicationNamespaces = + [ + "Queries", + "Application", + "Commands" + ]; + + private readonly string[] _domainNamespaces = + [ + "Domain" + ]; + + private readonly string[] _infrastructureNamespaces = + [ + "Infrastructure", + "Persistence" + ]; + + [Fact] + public void Domain_Should_Not_Depend_On_Infrastructure() + { + // Arrange + var domainTypes = TypesMatchingAnyPattern(_domainNamespaces); + var infraTypes = TypesMatchingAnyPattern(_infrastructureNamespaces); + + // Act + var result = domainTypes + .ShouldNot() + .HaveDependencyOnAny(infraTypes.GetNames()) + .GetResult(); + + // Assert + result.DumpFailingTypes(_outputHelper); + result.IsSuccessful.Should().BeTrue(); + } + + [Fact] + public void Domain_Should_Not_Depend_On_Application() + { + // Arrange + var domainTypes = TypesMatchingAnyPattern(_domainNamespaces); + var applicationTypes = TypesMatchingAnyPattern(_applicationNamespaces); + + // Act + var result = domainTypes + .ShouldNot() + .HaveDependencyOnAny(applicationTypes.GetNames()) + .GetResult(); + + // Assert + result.DumpFailingTypes(_outputHelper); + result.IsSuccessful.Should().BeTrue(); + } +} diff --git a/tests/VerticalSliceArchitecture.ArchTests/Common/TestResultExtensions.cs b/tests/VerticalSliceArchitecture.ArchTests/Common/TestResultExtensions.cs new file mode 100644 index 0000000..a0e16df --- /dev/null +++ b/tests/VerticalSliceArchitecture.ArchTests/Common/TestResultExtensions.cs @@ -0,0 +1,18 @@ +using NetArchTest.Rules; +using Xunit.Abstractions; + +namespace VerticalSliceArchitecture.ArchTests.Common; + +public static class TestResultExtensions +{ + public static void DumpFailingTypes(this TestResult result, ITestOutputHelper outputHelper) + { + if (result.IsSuccessful) + return; + + outputHelper.WriteLine("Failing Types:"); + + foreach (var type in result.FailingTypes) + outputHelper.WriteLine(type.FullName); + } +} diff --git a/tests/VerticalSliceArchitecture.ArchTests/GlobalUsings.cs b/tests/VerticalSliceArchitecture.ArchTests/GlobalUsings.cs new file mode 100644 index 0000000..8c927eb --- /dev/null +++ b/tests/VerticalSliceArchitecture.ArchTests/GlobalUsings.cs @@ -0,0 +1 @@ +global using Xunit; \ No newline at end of file diff --git a/tests/VerticalSliceArchitecture.ArchTests/PredicateListExt.cs b/tests/VerticalSliceArchitecture.ArchTests/PredicateListExt.cs new file mode 100644 index 0000000..fb3b147 --- /dev/null +++ b/tests/VerticalSliceArchitecture.ArchTests/PredicateListExt.cs @@ -0,0 +1,11 @@ +using NetArchTest.Rules; + +namespace VerticalSliceArchitecture.ArchTests; + +public static class PredicateListExt +{ + public static string?[] GetNames(this PredicateList list) + { + return list.GetTypes().Select(x => x.FullName).ToArray(); + } +} \ No newline at end of file diff --git a/tests/VerticalSliceArchitecture.ArchTests/TestBase.cs b/tests/VerticalSliceArchitecture.ArchTests/TestBase.cs new file mode 100644 index 0000000..6caa9f0 --- /dev/null +++ b/tests/VerticalSliceArchitecture.ArchTests/TestBase.cs @@ -0,0 +1,28 @@ +using NetArchTest.Rules; +using System.Diagnostics; + +namespace VerticalSliceArchitecture.ArchTests; + +public abstract class TestBase +{ + private static readonly Types ProgramTypes = Types.InAssembly(typeof(Program).Assembly); + + protected PredicateList TypesMatchingAnyPattern(params string[] patterns) + { + var output = ProgramTypes.That(); + + for (var index = 0; index < patterns.Length; index++) + { + var pattern = patterns[index]; + + if (index == patterns.Length - 1) + { + return output.ResideInNamespaceContaining(pattern); + } + + output = output.ResideInNamespaceContaining(pattern).Or(); + } + + throw new UnreachableException(); + } +} diff --git a/tests/VerticalSliceArchitecture.ArchTests/VerticalSliceArchitecture.ArchTests.csproj b/tests/VerticalSliceArchitecture.ArchTests/VerticalSliceArchitecture.ArchTests.csproj new file mode 100644 index 0000000..a9fee2c --- /dev/null +++ b/tests/VerticalSliceArchitecture.ArchTests/VerticalSliceArchitecture.ArchTests.csproj @@ -0,0 +1,31 @@ + + + + net8.0 + enable + enable + + false + true + + + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + +