Skip to content

Commit

Permalink
🧪 Architecture tests to enforce Domain purity (#87)
Browse files Browse the repository at this point in the history
  • Loading branch information
danielmackay authored May 16, 2024
1 parent d16a9b8 commit 8ed2291
Show file tree
Hide file tree
Showing 9 changed files with 169 additions and 2 deletions.
7 changes: 7 additions & 0 deletions VerticalSliceArchitectureTemplate.sln
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Original file line number Diff line number Diff line change
Expand Up @@ -29,4 +29,4 @@ public void Complete()

StagedEvents.Add(new TodoCompletedEvent(Id));
}
}
}
4 changes: 3 additions & 1 deletion src/VerticalSliceArchitectureTemplate/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -36,4 +36,6 @@

app.RegisterEndpoints(appAssembly);

app.Run();
app.Run();

public partial class Program;
69 changes: 69 additions & 0 deletions tests/VerticalSliceArchitecture.ArchTests/ArchitectureTests.cs
Original file line number Diff line number Diff line change
@@ -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();
}
}
Original file line number Diff line number Diff line change
@@ -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);
}
}
1 change: 1 addition & 0 deletions tests/VerticalSliceArchitecture.ArchTests/GlobalUsings.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
global using Xunit;
11 changes: 11 additions & 0 deletions tests/VerticalSliceArchitecture.ArchTests/PredicateListExt.cs
Original file line number Diff line number Diff line change
@@ -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();
}
}
28 changes: 28 additions & 0 deletions tests/VerticalSliceArchitecture.ArchTests/TestBase.cs
Original file line number Diff line number Diff line change
@@ -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();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>

<IsPackable>false</IsPackable>
<IsTestProject>true</IsTestProject>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="FluentAssertions" Version="6.12.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.6.0"/>
<PackageReference Include="NetArchTest.Rules" Version="1.3.2" />
<PackageReference Include="xunit" Version="2.4.2"/>
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.5">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="coverlet.collector" Version="6.0.0">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\..\src\VerticalSliceArchitectureTemplate\VerticalSliceArchitectureTemplate.csproj" />
</ItemGroup>

</Project>

0 comments on commit 8ed2291

Please sign in to comment.