Independently where you work, there is solution architectural standards that need to followed and enhanced. There might be guidelines, such as avoiding the inclusion of contract dependencies in the repository layer or ensuring that files implementing an interface have a 'command' suffix in their names.
While documenting and communicating these rules collectively may be sufficient in some cases, the risk of non-standard implementations increases as the company expands and more teams become involved. In such situations, it would be beneficial to have a framework that assists developers in adhering to the solution design standards.
One resource that can assist in this process is NetArchTest, a .NET library designed for implementing and enforcing architectural guidelines. By integrating this library with unit tests, it ensures that any violation of architectural principles is flagged and explained, leading to a failed test.
Lets look at an example.
Lets say we have the following design rules to adhere to:
- All repositories should be located in the repository folder.
- Contracts should not be directly referenced in repositories
- All repositories should have a name-suffix of repository
Lets apply this rules to the following solution.
First of I want to add some extensions methods that defines the different rules.
public static class PolicyExtensions
{
public static PolicyDefinition RepositoriesShouldBeLocatedInRepositoriesFolder(this PolicyDefinition policyDefinition)
{
return policyDefinition.Add(t =>
t.That()
.AreClasses()
.And()
.HaveNameMatching("Repository")
.Or().ImplementInterface(typeof(IRepository))
.Should()
.ResideInNamespace("Infrastructure.Repositories"),
"Repositories should be located in the repository folder.",
"To ensure repositories are easy to locate");
}
public static PolicyDefinition RepositoriesShouldHaveNamingSuffixRepository(this PolicyDefinition policyDefinition)
{
return policyDefinition.Add(t =>
t.That()
.ResideInNamespace("Infrastructure.Repositories")
.Should().HaveNameEndingWith("Repository"),
"All repositories should have a name-suffix of repository ",
"To comply with the naming conversions for repositories.");
}
public static PolicyDefinition ContractsShouldNotBeReferencedInsideRepositories(this PolicyDefinition policyDefinition)
{
return policyDefinition.Add(t =>
t.That()
.ResideInNamespace("Infrastructure.Repositories")
.ShouldNot()
.HaveDependencyOn("Contracts"),
"Contracts should not be directly referenced in repositories",
"A separation between presentation and persistence"
);
}
}
It is relatively simple to incorporate methods like these. I will now establish a new policy and apply these rules.
public static readonly PolicyDefinition MyPolicies =
Policy.Define("My policies", "This policies defines the architectural rules")
.For(Types.InAssembly(typeof(IRepository).Assembly))
.ContractsShouldNotBeReferencedInsideRepositories()
.RepositoriesShouldHaveNamingSuffixRepository()
.RepositoriesShouldBeLocatedInRepositoriesFolder();
Now lets execute a unittest that is using this policy.
[Test]
public async Task Should_follow_the_architectural_rules()
{
var policyResults = Policies.MyPolicies.Evaluate();
await Policies.ReportAsync(policyResults, Console.Out);
Assert.False(policyResults.HasViolations);
}
If the rules fail, I've used a console log that will show which types are not adhering to the rules. Running this on the following solution would result 3 rule violations. It will look like this:
That pretty much it.
Conclusion. To enforce solution design structure by using unittest is not something new. While you could write your own generic code library to do this (like many other libraries), NetArchTest is a well written, easy to use, and comes with very readable interface that gets you going in minutes.
More info about NetArchTest can be found here: https:/github.com/BenMorris/NetArchTest
Happy Coding!