Maintaining Architectural Integrity in .NET Projects with NetArchTest

Sugumar Panneerselvam
4 min readMay 26, 2024

--

Photo by Daniel McCullough on Unsplash

Introduction

Overview of Architectural Testing

Maintaining a clean architecture is crucial in software development to ensure scalability, maintainability, and readability of the code. Architectural testing helps enforce design principles by validating the structure and dependencies within a project. This ensures that the codebase adheres to predefined architectural rules, preventing the introduction of anti-patterns and architectural drift.

Introduction to NetArchTest

NetArchTest is a powerful tool designed for the .NET ecosystem that allows developers to enforce architectural rules through unit tests. It provides a fluent API to define and check architectural constraints, helping teams maintain the integrity of their software architecture over time.

What is NetArchTest?

Purpose

NetArchTest is designed to help developers enforce architectural constraints and dependencies within their codebase. By using unit tests, developers can ensure that the architecture adheres to predefined rules, promoting a clean and maintainable code structure.

Target Audience

NetArchTest is particularly useful for software architects, developers, and development teams focused on maintaining a clean architecture in their projects. It is an essential tool for anyone who wants to ensure that their codebase complies with architectural guidelines and principles.

Key Features of NetArchTest

Fluent API

NetArchTest features a fluent API that makes it intuitive and easy to use. This design allows developers to define and enforce architectural rules in a readable and maintainable way. The fluent interface helps in writing expressive and clear tests that are easy to understand and maintain.

.NET Standard Compatibility

NetArchTest is compatible with .NET Standard, making it versatile and usable across different .NET implementations, including .NET Core, .NET Framework, and Xamarin. This broad compatibility ensures that it can be integrated into various projects regardless of their specific .NET runtime.

Benefits of Using NetArchTest

Automated Enforcement

NetArchTest automates the enforcement of architectural rules, reducing the likelihood of human error. By incorporating architectural tests into the build process, teams can continuously validate the architecture, ensuring compliance without manual intervention.

Improved Code Quality

Consistently applying architectural rules with NetArchTest helps maintain high code quality. It ensures that the code adheres to design principles, making it easier to maintain, extend, and understand. This leads to more robust and reliable software.

Early Detection of Violations

NetArchTest allows developers to catch architectural violations early in the development process. By integrating architectural tests into the development workflow, teams can identify and address issues before they escalate, saving time and effort in the long run.

How to Get Started with NetArchTest

Installation

To get started with NetArchTest, you need to install it via NuGet. You can do this using the NuGet Package Manager in Visual Studio or by running the following command in the Package Manager Console:

Install-Package NetArchTest

Basic Usage

Here’s an example of how to use NetArchTest to define and enforce a simple architectural rule:

using NetArchTest.Rules;

public class ArchitectureTests
{
[Fact]
public void Services_ShouldNotDependOnControllers()
{
var result = Types
.InAssembly(typeof(SomeService).Assembly)
.That()
.ResideInNamespace("MyProject.Services")
.ShouldNot()
.HaveDependencyOn("MyProject.Controllers")
.GetResult();

Assert.True(result.IsSuccessful, "Services should not depend on Controllers.");
}
}

This example defines a rule that ensures classes in the Services namespace do not depend on classes in the Controllers namespace.

Advanced Scenarios

Complex Rules

NetArchTest supports the definition and enforcement of more complex architectural rules. For example, you can enforce layering rules, ensure naming conventions, and prevent unwanted dependencies across different parts of the application:

using NetArchTest.Rules;

public class AdvancedArchitectureTests
{
[Fact]
public void Domain_ShouldNotDependOnInfrastructure()
{
var result = Types
.InAssembly(typeof(DomainClass).Assembly)
.That()
.ResideInNamespace("MyProject.Domain")
.ShouldNot()
.HaveDependencyOn("MyProject.Infrastructure")
.GetResult();

Assert.True(result.IsSuccessful, "Domain should not depend on Infrastructure.");
}

[Fact]
public void Controllers_ShouldResideInControllersNamespace()
{
var result = Types
.InAssembly(typeof(SomeController).Assembly)
.That()
.ImplementInterface(typeof(IController))
.Should()
.ResideInNamespace("MyProject.Controllers")
.GetResult();

Assert.True(result.IsSuccessful, "Controllers should reside in the Controllers namespace.");
}

[Fact]
public void Classes_ShouldFollowNamingConventions()
{
var result = Types
.InAssembly(typeof(SomeClass).Assembly)
.That()
.HaveNameMatching(".*Service$")
.Should()
.ImplementInterface(typeof(IService))
.GetResult();

Assert.True(result.IsSuccessful, "Classes with names ending in 'Service' should implement the IService interface.");
}
}

Custom Rules

NetArchTest allows developers to create custom rules to enforce specific architectural requirements by implementing the ICustomRule interface. Here’s an example of how to implement and use a custom rule:

Implementing a Custom Rule

Create a custom rule by implementing the ICustomRule interface:

namespace NetArchTest.SampleRules
{
using NetArchTest.Rules;

public class NoPublicFieldsRule : ICustomRule
{
public bool MeetsRule(TypeDefinition type)
{
return !type.Fields.Any(f => f.IsPublic);
}
}
}

In this example, the NoPublicFieldsRule custom rule checks each type to ensure it does not have public fields.

Using a Custom Rule in Tests

Use the custom rule in your architectural tests:

using NetArchTest.Rules;
using NetArchTest.SampleRules;
using Xunit;

public class CustomArchitectureTests
{
[Fact]
public void Managers_ShouldNotHavePublicFields()
{
var result = Types
.InAssembly(typeof(SomeManagerClass).Assembly)
.That()
.HaveNameEndingWith("Manager")
.Should()
.MeetCustomRule(new NoPublicFieldsRule())
.GetResult();

Assert.True(result.IsSuccessful, "Classes ending with 'Manager' should not have public fields.");
}
}

In this example:

  • NoPublicFieldsRule: This custom rule ensures that classes do not have public fields.
  • Managers_ShouldNotHavePublicFields: This test checks that classes with names ending in “Manager” do not have public fields by applying the custom rule.

Best Practices

Consistent Rule Definition

Define architectural rules consistently across the team to ensure everyone adheres to the same standards. Document these rules and include them in your project’s guidelines.

Regular Reviews

Regularly review and update architectural rules to adapt to evolving project requirements and changes in the codebase. This ensures that the rules remain relevant and effective.

Collaboration

Encourage collaboration between architects and developers when defining architectural rules. This ensures that the rules are practical, realistic, and aligned with the team’s development practices and project goals.

Conclusion

By following this detailed outline with the added examples and custom rule implementation, you can write a comprehensive and informative article on NetArchTest, helping other developers understand its importance and how to effectively use it in their projects.

Try out NetArchTest in your own .NET projects to experience its benefits firsthand. Consider contributing to its development to help enhance its capabilities and support the community.

In the next article, we will cover ArchUnit for Java, exploring its features and how it helps in maintaining architectural integrity in Java projects.

References

--

--