forked from kgrzybek/modular-monolith-with-ddd
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add Architecture Tests for Application layer and Domain layer for Mee…
…tings module
- Loading branch information
Showing
41 changed files
with
572 additions
and
99 deletions.
There are no files selected for viewing
123 changes: 123 additions & 0 deletions
123
...CompanyName.MyMeetings.Modules.Meetings.ArchitectureTests/Application/ApplicationTests.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,123 @@ | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Reflection; | ||
using CompanyName.MyMeetings.Modules.Meetings.Application.Configuration.Processing; | ||
using CompanyName.MyMeetings.Modules.Meetings.Application.Configuration.Processing.InternalCommands; | ||
using CompanyName.MyMeetings.Modules.Meetings.Application.Contracts; | ||
using CompanyName.MyMeetings.Modules.Meetings.ArchitectureTests.SeedWork; | ||
using NetArchTest.Rules; | ||
using Newtonsoft.Json; | ||
using NUnit.Framework; | ||
|
||
namespace CompanyName.MyMeetings.Modules.Meetings.ArchitectureTests.Application | ||
{ | ||
[TestFixture] | ||
public class ApplicationTests : TestBase | ||
{ | ||
[Test] | ||
public void Command_Should_Be_Immutable() | ||
{ | ||
var types = Types.InAssembly(ApplicationAssembly) | ||
.That().Inherit(typeof(CommandBase)) | ||
.Or().Inherit(typeof(InternalCommandBase)) | ||
.Or().ImplementInterface(typeof(ICommand)) | ||
.Or().ImplementInterface(typeof(ICommand<>)) | ||
.GetTypes(); | ||
|
||
AssertAreImmutable(types); | ||
} | ||
|
||
[Test] | ||
public void Query_Should_Be_Immutable() | ||
{ | ||
var types = Types.InAssembly(ApplicationAssembly) | ||
.That().ImplementInterface(typeof(IQuery<>)).GetTypes(); | ||
|
||
AssertAreImmutable(types); | ||
} | ||
|
||
[Test] | ||
public void CommandHandler_Should_Have_Name_EndingWith_CommandHandler() | ||
{ | ||
var result = Types.InAssembly(ApplicationAssembly) | ||
.That() | ||
.ImplementInterface(typeof(ICommandHandler<>)) | ||
.And() | ||
.DoNotHaveNameMatching(".*Decorator.*").Should() | ||
.HaveNameEndingWith("CommandHandler") | ||
.GetResult(); | ||
|
||
AssertArchTestResult(result); | ||
} | ||
|
||
[Test] | ||
public void QueryHandler_Should_Have_Name_EndingWith_QueryHandler() | ||
{ | ||
var result = Types.InAssembly(ApplicationAssembly) | ||
.That() | ||
.ImplementInterface(typeof(IQueryHandler<,>)) | ||
.Should() | ||
.HaveNameEndingWith("QueryHandler") | ||
.GetResult(); | ||
|
||
AssertArchTestResult(result); | ||
} | ||
|
||
[Test] | ||
public void InternalCommands_Should_Not_Be_Public() | ||
{ | ||
var result = Types.InAssembly(ApplicationAssembly) | ||
.That() | ||
.Inherit(typeof(InternalCommandBase)) | ||
.Should() | ||
.NotBePublic() | ||
.GetResult(); | ||
|
||
AssertArchTestResult(result); | ||
} | ||
|
||
[Test] | ||
public void Command_And_Query_Handlers_Should_Not_Be_Public() | ||
{ | ||
var types = Types.InAssembly(ApplicationAssembly) | ||
.That() | ||
.ImplementInterface(typeof(IQueryHandler<,>)) | ||
.Or() | ||
.ImplementInterface(typeof(ICommandHandler<>)) | ||
.Should().NotBePublic().GetResult().FailingTypes; | ||
|
||
AssertFailingTypes(types); | ||
} | ||
|
||
[Test] | ||
public void InternalCommand_Should_Have_Internal_Constructor_With_JsonConstructorAttribute() | ||
{ | ||
var types = Types.InAssembly(ApplicationAssembly) | ||
.That().Inherit(typeof(InternalCommandBase)).GetTypes(); | ||
|
||
var failingTypes = new List<Type>(); | ||
|
||
foreach (var type in types) | ||
{ | ||
bool hasJsonConstructorDefined = false; | ||
var constructors = type.GetConstructors(BindingFlags.NonPublic | BindingFlags.Instance); | ||
foreach (var constructorInfo in constructors) | ||
{ | ||
var jsonConstructorAttribute = constructorInfo.GetCustomAttributes(typeof(JsonConstructorAttribute), false); | ||
if (jsonConstructorAttribute.Length > 0) | ||
{ | ||
hasJsonConstructorDefined = true; | ||
break; | ||
} | ||
} | ||
|
||
if (!hasJsonConstructorDefined) | ||
{ | ||
failingTypes.Add(type); | ||
} | ||
} | ||
|
||
AssertFailingTypes(failingTypes); | ||
} | ||
} | ||
} |
23 changes: 23 additions & 0 deletions
23
...etings.ArchitectureTests/CompanyName.MyMeetings.Modules.Meetings.ArchitectureTests.csproj
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
<Project Sdk="Microsoft.NET.Sdk"> | ||
|
||
<PropertyGroup> | ||
<TargetFramework>netcoreapp2.2</TargetFramework> | ||
|
||
<IsPackable>false</IsPackable> | ||
</PropertyGroup> | ||
|
||
<ItemGroup> | ||
<PackageReference Include="Autofac" Version="4.9.4" /> | ||
<PackageReference Include="NetArchTest.Rules" Version="1.2.3" /> | ||
<PackageReference Include="nunit" Version="3.11.0" /> | ||
<PackageReference Include="NUnit3TestAdapter" Version="3.11.0" /> | ||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.9.0" /> | ||
</ItemGroup> | ||
|
||
<ItemGroup> | ||
<ProjectReference Include="..\BuildingBlocks\Domain\CompanyName.MyMeetings.BuildingBlocks.Domain.csproj" /> | ||
<ProjectReference Include="..\Modules\Meetings\Application\CompanyName.MyMeetings.Modules.Meetings.Application.csproj" /> | ||
<ProjectReference Include="..\Modules\Meetings\Domain\CompanyName.MyMeetings.Modules.Meetings.Domain.csproj" /> | ||
</ItemGroup> | ||
|
||
</Project> |
206 changes: 206 additions & 0 deletions
206
src/CompanyName.MyMeetings.Modules.Meetings.ArchitectureTests/Domain/DomainTests.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,206 @@ | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Linq; | ||
using System.Reflection; | ||
using CompanyName.MyMeetings.BuildingBlocks.Domain; | ||
using CompanyName.MyMeetings.Modules.Meetings.ArchitectureTests.SeedWork; | ||
using NetArchTest.Rules; | ||
using NUnit.Framework; | ||
|
||
namespace CompanyName.MyMeetings.Modules.Meetings.ArchitectureTests.Domain | ||
{ | ||
public class DomainTests : TestBase | ||
{ | ||
[Test] | ||
public void DomainEvent_Should_Be_Immutable() | ||
{ | ||
var types = Types.InAssembly(DomainAssembly) | ||
.That() | ||
.Inherit(typeof(DomainEventBase)) | ||
.Or() | ||
.Inherit(typeof(IDomainEvent)) | ||
.GetTypes(); | ||
|
||
AssertAreImmutable(types); | ||
} | ||
|
||
[Test] | ||
public void ValueObject_Should_Be_Immutable() | ||
{ | ||
var types = Types.InAssembly(DomainAssembly) | ||
.That() | ||
.Inherit(typeof(ValueObject)) | ||
.GetTypes(); | ||
|
||
AssertAreImmutable(types); | ||
} | ||
|
||
[Test] | ||
public void Entity_Which_Is_Not_Aggregate_Root_Cannot_Have_Public_Members() | ||
{ | ||
var types = Types.InAssembly(DomainAssembly) | ||
.That() | ||
.Inherit(typeof(Entity)) | ||
.And().DoNotImplementInterface(typeof(IAggregateRoot)).GetTypes(); | ||
|
||
const BindingFlags bindingFlags = BindingFlags.DeclaredOnly | | ||
BindingFlags.Public | | ||
BindingFlags.Instance | | ||
BindingFlags.Static; | ||
|
||
var failingTypes = new List<Type>(); | ||
foreach (var type in types) | ||
{ | ||
var publicFields = type.GetFields(bindingFlags); | ||
var publicProperties = type.GetProperties(bindingFlags); | ||
var publicMethods = type.GetMethods(bindingFlags); | ||
|
||
if (publicFields.Any() || publicProperties.Any() || publicMethods.Any()) | ||
{ | ||
failingTypes.Add(type); | ||
} | ||
} | ||
|
||
AssertFailingTypes(failingTypes); | ||
} | ||
|
||
[Test] | ||
public void Entity_Cannot_Have_Reference_To_Other_AggregateRoot() | ||
{ | ||
var entityTypes = Types.InAssembly(DomainAssembly) | ||
.That() | ||
.Inherit(typeof(Entity)).GetTypes(); | ||
|
||
var aggregateRoots = Types.InAssembly(DomainAssembly) | ||
.That().ImplementInterface(typeof(IAggregateRoot)).GetTypes().ToList(); | ||
|
||
const BindingFlags bindingFlags = BindingFlags.DeclaredOnly | | ||
BindingFlags.NonPublic | | ||
BindingFlags.Instance; | ||
|
||
var failingTypes = new List<Type>(); | ||
foreach (var type in entityTypes) | ||
{ | ||
var fields = type.GetFields(bindingFlags); | ||
|
||
foreach (var field in fields) | ||
{ | ||
if (aggregateRoots.Contains(field.FieldType) || | ||
field.FieldType.GenericTypeArguments.Any(x => aggregateRoots.Contains(x))) | ||
{ | ||
failingTypes.Add(type); | ||
break; | ||
} | ||
} | ||
|
||
var properties = type.GetProperties(bindingFlags); | ||
foreach (var property in properties) | ||
{ | ||
if (aggregateRoots.Contains(property.PropertyType) || | ||
property.PropertyType.GenericTypeArguments.Any(x => aggregateRoots.Contains(x))) | ||
{ | ||
failingTypes.Add(type); | ||
break; | ||
} | ||
} | ||
} | ||
|
||
AssertFailingTypes(failingTypes); | ||
} | ||
|
||
[Test] | ||
public void Entity_Should_Have_Parameterless_Private_Constructor() | ||
{ | ||
var entityTypes = Types.InAssembly(DomainAssembly) | ||
.That() | ||
.Inherit(typeof(Entity)).GetTypes(); | ||
|
||
var failingTypes = new List<Type>(); | ||
foreach (var entityType in entityTypes) | ||
{ | ||
bool hasPrivateParameterlessConstructor = false; | ||
var constructors = entityType.GetConstructors(BindingFlags.NonPublic | BindingFlags.Instance); | ||
foreach (var constructorInfo in constructors) | ||
{ | ||
if (constructorInfo.IsPrivate && constructorInfo.GetParameters().Length == 0) | ||
{ | ||
hasPrivateParameterlessConstructor = true; | ||
} | ||
} | ||
|
||
if (!hasPrivateParameterlessConstructor) | ||
{ | ||
failingTypes.Add(entityType); | ||
} | ||
} | ||
|
||
AssertFailingTypes(failingTypes); | ||
} | ||
|
||
[Test] | ||
public void Domain_Object_Should_Have_Only_Private_Constructors() | ||
{ | ||
var domainObjectTypes = Types.InAssembly(DomainAssembly) | ||
.That() | ||
.Inherit(typeof(Entity)) | ||
.Or() | ||
.Inherit(typeof(ValueObject)) | ||
.GetTypes(); | ||
|
||
var failingTypes = new List<Type>(); | ||
foreach (var domainObjectType in domainObjectTypes) | ||
{ | ||
var constructors = domainObjectType.GetConstructors(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance); | ||
foreach (var constructorInfo in constructors) | ||
{ | ||
|
||
if (!constructorInfo.IsPrivate) | ||
{ | ||
failingTypes.Add(domainObjectType); | ||
} | ||
} | ||
} | ||
|
||
AssertFailingTypes(failingTypes); | ||
} | ||
|
||
[Test] | ||
public void ValueObject_Should_Have_Private_Constructor_With_Parameters_For_His_State() | ||
{ | ||
var valueObjects = Types.InAssembly(DomainAssembly) | ||
.That() | ||
.Inherit(typeof(ValueObject)).GetTypes(); | ||
|
||
var failingTypes = new List<Type>(); | ||
foreach (var entityType in valueObjects) | ||
{ | ||
bool hasExpectedConstructor = false; | ||
|
||
const BindingFlags bindingFlags = BindingFlags.DeclaredOnly | | ||
BindingFlags.Public | | ||
BindingFlags.Instance; | ||
var names = entityType.GetFields(bindingFlags).Select(x => x.Name.ToLower()).ToList(); | ||
var propertyNames = entityType.GetProperties(bindingFlags).Select(x => x.Name.ToLower()).ToList(); | ||
names.AddRange(propertyNames); | ||
var constructors = entityType.GetConstructors(BindingFlags.NonPublic | BindingFlags.Instance); | ||
foreach (var constructorInfo in constructors) | ||
{ | ||
var parameters = constructorInfo.GetParameters().Select( x => x.Name.ToLower()).ToList(); | ||
|
||
if (names.Intersect(parameters).Count() == names.Count) | ||
{ | ||
hasExpectedConstructor = true; | ||
break; | ||
} | ||
} | ||
|
||
if (!hasExpectedConstructor) | ||
{ | ||
failingTypes.Add(entityType); | ||
} | ||
} | ||
|
||
AssertFailingTypes(failingTypes); | ||
} | ||
} | ||
} |
41 changes: 41 additions & 0 deletions
41
src/CompanyName.MyMeetings.Modules.Meetings.ArchitectureTests/SeedWork/TestBase.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Linq; | ||
using System.Reflection; | ||
using CompanyName.MyMeetings.Modules.Meetings.Application.Contracts; | ||
using CompanyName.MyMeetings.Modules.Meetings.Domain.MeetingGroupProposals; | ||
using NetArchTest.Rules; | ||
using NUnit.Framework; | ||
|
||
namespace CompanyName.MyMeetings.Modules.Meetings.ArchitectureTests.SeedWork | ||
{ | ||
public abstract class TestBase | ||
{ | ||
protected static Assembly ApplicationAssembly = typeof(CommandBase).Assembly; | ||
protected static Assembly DomainAssembly = typeof(MeetingGroupProposal).Assembly; | ||
|
||
protected static void AssertAreImmutable(IEnumerable<Type> types) | ||
{ | ||
IList<Type> failingTypes = new List<Type>(); | ||
foreach (var type in types) | ||
{ | ||
if (type.GetFields().Any(x => !x.IsInitOnly) || type.GetProperties().Any(x => x.CanWrite)) | ||
{ | ||
failingTypes.Add(type); | ||
break; | ||
} | ||
} | ||
|
||
AssertFailingTypes(failingTypes); | ||
} | ||
protected static void AssertFailingTypes(IEnumerable<Type> types) | ||
{ | ||
Assert.That(types, Is.Null.Or.Empty); | ||
} | ||
|
||
protected static void AssertArchTestResult(TestResult result) | ||
{ | ||
AssertFailingTypes(result.FailingTypes); | ||
} | ||
} | ||
} |
Oops, something went wrong.