Enforcing software architecture rules for .net project using unit tests

Posted on March 19, 2023
testingarchitecturecleanarchitectureseriesonion-architectureunit testSoftware architecture rulesArchitecture enforcementDependency injectionArchitecture validationAutomated testing

Read the onion architecture article located here since the remainder of this article will focus on writing a few architecture unit tests to ensure that the onion architecture is adhered to

What do you mean by software architecture rules?

software architecture rules are best practices that tell how software should be designed and organized. These rules cover many aspects i.e. overall system structure, segregation of responsibilities, and integration between different application components.

Who are stack holders responsible for Enforcing software architecture rules?

  1. Software Architects
  2. Developers
  3. Project Managers
  4. Quality assurance personnel and other members of the development team

Why software architecture rules?

All stakeholders are not necessarily required to memorize all the software architecture rules. However, it is important for all members of the development team to have a general understanding of the rules and their importance in ensuring the quality and reliability of the system.

In addition, even if code reviews are conducted, there is still a possibility that some unintentional violations of the software architecture rules may not be caught. This is where the use of unit tests can be beneficial. By writing unit tests that specifically target the software architecture rules, developers can ensure that the system adheres to those rules and catch any violations early on in the development process.

If a stakeholder who was responsible for enforcing software architecture rules leaves the company, it is important to ensure that their knowledge and responsibilities are transferred to someone else on the team and additionally can help identify violations of the software architecture rules and alert the development team to potential issues.

Getting Started

Read the onion architecture article located here, we will focus on writing unit tests to enforce rules in the project. The example below uses ArchUnitNET and xUnit.

PS> Install-Package ArchUnitNET.xUnit
PS> Install-Package ArchUnitNET.NUnit
PS> Install-Package ArchUnitNET.MSTestV2

Adding Rules/Guideline

Rule 01: The domain layer should not have a reference to any other layer within the application core.

Rule 02: The domain layer should not have references to the repositories layer.

Rule 03: The domain layer should not have references to the Presentation layer.

Rule 04: The domain layer should not have references to the persistence layer.

Rule 05: The Presentation Layer should have a reference to the domain layer

Test Run - Passed

Test Run Output

Sample Unit Test

using ArchUnitNET.Domain;
using ArchUnitNET.Loader;
using ArchUnitNET.Fluent;


using static ArchUnitNET.Fluent.ArchRuleDefinition;
using ArchUnitNET.Domain.Extensions;
using ArchUnitNET.MSTestV2;
using Autofac;
using OnionDemo.Persistance;
using OnionDemo.Services;

namespace OnionDemo.Core.Policies.Tests
{
    public class ArchitectureViolationsRuleTest
    {
        private static readonly Architecture Architecture = new ArchLoader().LoadAssemblies(
               System.Reflection.Assembly.Load("OnionDemo.Domain"),
               System.Reflection.Assembly.Load("OnionDemo.Services"),
               System.Reflection.Assembly.Load("OnionDemo.Persistance"),
               System.Reflection.Assembly.Load("OnionDemo.Presentation")).Build();

        private readonly IObjectProvider<IType> DomainModelLayer = Types().That().ResideInAssembly("OnionDemo.Domain");

        private readonly IObjectProvider<IType> DomainServiceLayer = Types().That().ResideInNamespace("OnionDemo.Services");

        private readonly IObjectProvider<IType> PersistanceLayer = Types().That().ResideInNamespace("OnionDemo.Persistance");

        private readonly IObjectProvider<IType> PresentationLayer = Types().That().ResideInNamespace("OnionDemo.Presentation");

        [Fact]
        public void TestLayerShouldContainsPrerequisiteAbstractions()
        {
            var domainModelInterfaces = Architecture.Interfaces.Where(x =>
                            x.NameEndsWith("Repository") ||
                            x.NameEndsWith("Manager"));

            Assert.True(domainModelInterfaces.Any());
        }

        [Fact]
        public void TestLayerForViolations()
        {
            // Create a new container builder
            var builder = new ContainerBuilder();

            // Register the module to be tested
            builder.RegisterModule(new ServiceAutofacModule());
            builder.RegisterModule(new PersistanceAutofacModule());
            builder.Build();

            //The domain layer should not have reference to any other layer within the application core.
            IArchRule layerdRule = Types().That().Are(DomainModelLayer)
                .Should().NotDependOnAny(DomainServiceLayer)
                .Because("The domain model layer should not have reference to any other layer within the application core.");
            layerdRule.Check(Architecture);

            //Domain layer should not have references to the repositories layer.
            layerdRule = Types().That().Are(DomainModelLayer)
               .Should().NotDependOnAny(PersistanceLayer)
               .Because("The Domain layer should not have references to the repositories layer.");
            layerdRule.Check(Architecture);

            //Domain layer should not have references to the Presentation layer.
            layerdRule = Types().That().Are(DomainModelLayer)
               .Should().NotDependOnAny(PresentationLayer)
               .Because("The Domain layer should not have references to the Presentation layer.");
            layerdRule.Check(Architecture);

            //Domain layer should not have references to the Persistance layer.
            layerdRule = Types().That().Are(DomainServiceLayer)
                .Should().NotDependOnAny(PersistanceLayer)
                .Because("Domain layer should not have references to the Persistance layer.");

            layerdRule.Check(Architecture);


            //The Presentation Layer should have reference to domain layer
            layerdRule = Types().That().Are(PresentationLayer)
                .Should().DependOnAny(DomainModelLayer);
            
            layerdRule.Check(Architecture);
        }
    }
}

Note: New unit test added to existing project solution here Please download from Github. See the screenshot for reference.

New Unit Test Project

Summary

  • Software architecture rules are guidelines and constraints that define how the different components and layers of a software system should be organized and interact with each other, in order to achieve specific goals such as maintainability, scalability, and security.
  • The stakeholders responsible for enforcing software architecture rules in a .NET project may include architects, developers, quality assurance testers, project managers, and other members of the development team. Each stakeholder has a role to play in ensuring that the system conforms to the defined architecture rules.
  • Software architecture rules are important because they help ensure that a system is maintainable, scalable, and secure over time. By enforcing architecture rules, you can reduce technical debt, improve code quality, and avoid common architectural mistakes.
  • Some alternatives to ArchUnit for enforcing software architecture rules in .NET projects include NetArchTest, NDepend, SonarQube, and Structure101.

Thanks for reading!


Posted on March 19, 2023
Profile Picture

Arun Yadav

Software Architect | Full Stack Web Developer | Cloud/Containers

Subscribe
to our Newsletter

Signup for our weekly newsletter to get the latest news, articles and update in your inbox.