dotnet-archunitnet
Use the open-source free `ArchUnitNET` library for architecture rules in .NET tests. Use when a repo needs richer architecture assertions than lightweight fluent rule libraries usually provide.
Install
npx skills add https://github.com/Postpartum-genushyacinthus29/dotnet-skills/tree/main/skills/dotnet-archunitnet
claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install postpartum-genushyacinthus29-dotnet-skills@llmmart
git clone https://github.com/Postpartum-genushyacinthus29/dotnet-skills.git
The skills CLI installs just this skill, for any of its supported agents. Claude Code installs the whole postpartum-genushyacinthus29/dotnet-skills collection as a plugin from our marketplace. Git is the plain clone.
Skill manifest
ArchUnitNET for .NET
Trigger On
- the repo uses or wants
ArchUnitNET - architecture testing needs richer modeling than simple dependency checks
Value
- produce a concrete project delta: code, docs, config, tests, CI, or review artifact
- reduce ambiguity through explicit planning, verification, and final validation skills
- leave reusable project context so future tasks are faster and safer
Do Not Use For
- the lightest possible architecture rule checks
Inputs
- the nearest
AGENTS.md - target assemblies
- architecture boundaries and naming conventions
Quick Start
- Read the nearest
AGENTS.mdand confirm scope and constraints. - Run this skill's
Workflowthrough theRalph Loopuntil outcomes are acceptable. - Return the
Required Result Formatwith concrete artifacts and verification evidence.
Workflow
- Load the architecture once per test assembly where possible.
- Encode a small number of durable, high-value architecture rules first.
- Use the test-framework-specific integration package that matches the repo.
Bootstrap When Missing
If ArchUnitNET is not configured yet:
- Detect existing setup:
rg -n "TngTech\\.ArchUnitNET" -g '*.csproj' .
- Add packages to the architecture test project:
dotnet add TEST_PROJECT.csproj package TngTech.ArchUnitNET- add one framework bridge package:
TngTech.ArchUnitNET.xUnit,TngTech.ArchUnitNET.xUnitV3,TngTech.ArchUnitNET.MSTestV2, orTngTech.ArchUnitNET.TUnit
- Add at least one durable boundary rule test.
- Wire architecture tests into the standard
testcommand inAGENTS.mdand CI. - Run
dotnet test TEST_PROJECT.csprojand returnstatus: configuredorstatus: improved. - If
NetArchTestalready covers the same boundary policy and no gap exists, returnstatus: not_applicable.
Deliver
- architecture tests with richer domain and type modeling
- architecture-rule commands wired into repo test flow and CI expectations
Validate
- architecture load cost is reasonable for the suite
- rules are stable and tied to real boundaries
Ralph Loop
Use the Ralph Loop for every task, including docs, architecture, testing, and tooling work.
- Plan first (mandatory):
- analyze current state
- define target outcome, constraints, and risks
- write a detailed execution plan
- list final validation skills to run at the end, with order and reason
- Execute one planned step and produce a concrete delta.
- Review the result and capture findings with actionable next fixes.
- Apply fixes in small batches and rerun the relevant checks or review steps.
- Update the plan after each iteration.
- Repeat until outcomes are acceptable or only explicit exceptions remain.
- If a dependency is missing, bootstrap it or return
status: not_applicablewith explicit reason and fallback path.
Required Result Format
status:complete|clean|improved|configured|not_applicable|blockedplan: concise plan and current iteration stepactions_taken: concrete changes madevalidation_skills: final skills run, or skipped with reasonsverification: commands, checks, or review evidence summaryremaining: top unresolved items ornone
For setup-only requests with no execution, return status: configured and exact next commands.
Load References
references/archunitnet.mdreferences/patterns.mdreferences/examples.md
Example Requests
- "Use ArchUnitNET for layered architecture tests."
- "Set up ArchUnitNET with xUnit or MSTest."
Files (dotnet-skills)
-
references
-
archunitnet.md 1 KB
# ArchUnitNET ## Open/Free Status - open source - free to use ## Install Core package: ```bash dotnet add package TngTech.ArchUnitNET ``` Framework integration packages vary by test framework, for example: ```bash dotnet add package TngTech.ArchUnitNET.xUnit dotnet add package TngTech.ArchUnitNET.xUnitV3 dotnet add package TngTech.ArchUnitNET.MSTestV2 dotnet add package TngTech.ArchUnitNET.TUnit ``` ## Verify First Before adding packages, check whether the repo already references ArchUnitNET and which framework integration it uses: ```bash rg -n "TngTech\\.ArchUnitNET" -g '*.csproj' . ``` ## Common Usage Load the target assemblies once, then assert rules in tests. Good fit for: - layered architecture - namespace rules - dependency restrictions - domain boundary checks ## CI Fit - runs as part of the normal test suite - richer than lightweight architecture-rule libraries, but also heavier ## When Not To Use - when simple `NetArchTest` rules already cover the needed constraints ## Sources - [ArchUnitNET](https://github.com/TNG/ArchUnitNET) -
examples.md 10.1 KB
# ArchUnitNET Common Architecture Rules ## Project Setup ### Base Test Class ```csharp using ArchUnitNET.Domain; using ArchUnitNET.Loader; using ArchUnitNET.Fluent; using static ArchUnitNET.Fluent.ArchRuleDefinition; public abstract class ArchitectureTestBase { protected static readonly Architecture Architecture = new ArchLoader() .LoadAssemblies( typeof(Domain.Marker).Assembly, typeof(Application.Marker).Assembly, typeof(Infrastructure.Marker).Assembly, typeof(Web.Marker).Assembly) .Build(); } ``` ## Clean Architecture Rules ### Domain Layer Independence The domain layer should have no dependencies on outer layers: ```csharp [Fact] public void Domain_Should_Not_Reference_Application() { Types() .That().ResideInNamespace("MyApp.Domain") .Should().NotDependOnAny( Types().That().ResideInNamespace("MyApp.Application")) .Check(Architecture); } [Fact] public void Domain_Should_Not_Reference_Infrastructure() { Types() .That().ResideInNamespace("MyApp.Domain") .Should().NotDependOnAny( Types().That().ResideInNamespace("MyApp.Infrastructure")) .Check(Architecture); } [Fact] public void Domain_Should_Not_Reference_Presentation() { Types() .That().ResideInNamespace("MyApp.Domain") .Should().NotDependOnAny( Types().That().ResideInNamespace("MyApp.Web")) .Check(Architecture); } ``` ### Application Layer Rules ```csharp [Fact] public void Application_Should_Not_Reference_Infrastructure() { Types() .That().ResideInNamespace("MyApp.Application") .Should().NotDependOnAny( Types().That().ResideInNamespace("MyApp.Infrastructure")) .Check(Architecture); } [Fact] public void Application_Should_Not_Reference_Presentation() { Types() .That().ResideInNamespace("MyApp.Application") .Should().NotDependOnAny( Types().That().ResideInNamespace("MyApp.Web")) .Check(Architecture); } ``` ### Infrastructure Layer Rules ```csharp [Fact] public void Infrastructure_Should_Not_Reference_Presentation() { Types() .That().ResideInNamespace("MyApp.Infrastructure") .Should().NotDependOnAny( Types().That().ResideInNamespace("MyApp.Web")) .Check(Architecture); } ``` ## Naming Convention Rules ### Controllers ```csharp [Fact] public void Controllers_Should_Have_Controller_Suffix() { Types() .That().ResideInNamespace("Controllers", includeSubNamespaces: true) .And().AreClasses() .And().AreNotAbstract() .Should().HaveNameEndingWith("Controller") .Check(Architecture); } [Fact] public void Controllers_Should_Inherit_ControllerBase() { Types() .That().HaveNameEndingWith("Controller") .And().ResideInNamespace("Controllers") .Should().BeAssignableTo(typeof(ControllerBase)) .Check(Architecture); } ``` ### Services ```csharp [Fact] public void Services_Should_Have_Service_Suffix() { Types() .That().ResideInNamespace("Services") .And().AreClasses() .And().AreNotAbstract() .Should().HaveNameEndingWith("Service") .Check(Architecture); } [Fact] public void Service_Interfaces_Should_Start_With_I() { Types() .That().ResideInNamespace("Services") .And().AreInterfaces() .Should().HaveNameStartingWith("I") .Check(Architecture); } ``` ### Repositories ```csharp [Fact] public void Repositories_Should_Have_Repository_Suffix() { Types() .That().ImplementInterface(typeof(IRepository<>)) .And().AreClasses() .Should().HaveNameEndingWith("Repository") .Check(Architecture); } ``` ### Handlers (CQRS/MediatR) ```csharp [Fact] public void Handlers_Should_Have_Handler_Suffix() { Types() .That().ImplementInterface(typeof(IRequestHandler<,>)) .Should().HaveNameEndingWith("Handler") .Check(Architecture); } [Fact] public void Commands_Should_Have_Command_Suffix() { Types() .That().ImplementInterface(typeof(IRequest<>)) .And().ResideInNamespaceContaining("Commands") .Should().HaveNameEndingWith("Command") .Check(Architecture); } [Fact] public void Queries_Should_Have_Query_Suffix() { Types() .That().ImplementInterface(typeof(IRequest<>)) .And().ResideInNamespaceContaining("Queries") .Should().HaveNameEndingWith("Query") .Check(Architecture); } ``` ## Domain-Driven Design Rules ### Entities ```csharp [Fact] public void Entities_Should_Reside_In_Domain() { Types() .That().AreAssignableTo(typeof(Entity)) .Should().ResideInNamespaceContaining("Domain") .Check(Architecture); } [Fact] public void Entities_Should_Not_Have_Public_Setters() { // Use custom predicate or property-level checks Types() .That().AreAssignableTo(typeof(Entity)) .Should().BeSealed() .OrShould().BeAbstract() .Check(Architecture); } ``` ### Value Objects ```csharp [Fact] public void ValueObjects_Should_Be_Sealed() { Types() .That().AreAssignableTo(typeof(ValueObject)) .And().AreNotAbstract() .Should().BeSealed() .Check(Architecture); } ``` ### Aggregate Roots ```csharp [Fact] public void Only_AggregateRoots_Should_Be_Referenced_By_Repositories() { Types() .That().HaveNameEndingWith("Repository") .Should().DependOnAny( Types().That().ImplementInterface(typeof(IAggregateRoot))) .Check(Architecture); } ``` ## API Design Rules ### DTOs ```csharp [Fact] public void DTOs_Should_Not_Reference_Domain_Entities() { Types() .That().HaveNameEndingWith("Dto") .Or().HaveNameEndingWith("Request") .Or().HaveNameEndingWith("Response") .Should().NotDependOnAny( Types().That().ResideInNamespace("Domain.Entities")) .Check(Architecture); } ``` ### No Circular Dependencies ```csharp [Fact] public void Application_Should_Be_Free_Of_Cycles() { Types() .That().ResideInNamespace("MyApp.Application") .Should().BeFreeOfCycles() .Check(Architecture); } ``` ## Infrastructure Rules ### Database Contexts ```csharp [Fact] public void DbContexts_Should_Reside_In_Infrastructure() { Types() .That().AreAssignableTo(typeof(DbContext)) .Should().ResideInNamespaceContaining("Infrastructure") .Check(Architecture); } ``` ### External Service Clients ```csharp [Fact] public void HttpClients_Should_Reside_In_Infrastructure() { Types() .That().HaveNameEndingWith("Client") .And().DependOnAny(Types().That().HaveName("HttpClient")) .Should().ResideInNamespaceContaining("Infrastructure") .Check(Architecture); } ``` ## Dependency Injection Rules ### Interface Implementations ```csharp [Fact] public void Every_Service_Should_Have_Interface() { Types() .That().HaveNameEndingWith("Service") .And().AreClasses() .And().AreNotAbstract() .Should().ImplementInterface( Types().That().AreInterfaces() .And().HaveNameStartingWith("I")) .Check(Architecture); } ``` ## Testing Rules ### Test Classes Naming ```csharp [Fact] public void Test_Classes_Should_Have_Tests_Suffix() { Types() .That().ResideInAssembly(typeof(SomeTest).Assembly) .And().AreClasses() .And().HaveAnyMethodWithAttribute(typeof(FactAttribute)) .Should().HaveNameEndingWith("Tests") .Check(TestArchitecture); } ``` ## Complete Test Class Example ```csharp using ArchUnitNET.Domain; using ArchUnitNET.Loader; using ArchUnitNET.Fluent; using ArchUnitNET.xUnit; using Xunit; using static ArchUnitNET.Fluent.ArchRuleDefinition; namespace MyApp.Tests.Architecture; public class CleanArchitectureTests { private static readonly Architecture Architecture = new ArchLoader() .LoadAssemblies( typeof(Domain.DomainAssemblyMarker).Assembly, typeof(Application.ApplicationAssemblyMarker).Assembly, typeof(Infrastructure.InfrastructureAssemblyMarker).Assembly, typeof(Web.WebAssemblyMarker).Assembly) .Build(); // Layer definitions private static readonly IObjectProvider<IType> DomainLayer = Types().That().ResideInNamespace("MyApp.Domain").As("Domain Layer"); private static readonly IObjectProvider<IType> ApplicationLayer = Types().That().ResideInNamespace("MyApp.Application").As("Application Layer"); private static readonly IObjectProvider<IType> InfrastructureLayer = Types().That().ResideInNamespace("MyApp.Infrastructure").As("Infrastructure Layer"); private static readonly IObjectProvider<IType> PresentationLayer = Types().That().ResideInNamespace("MyApp.Web").As("Presentation Layer"); [Fact] public void Domain_Should_Have_No_Dependencies_On_Other_Layers() { Types() .That().Are(DomainLayer) .Should().NotDependOnAny(ApplicationLayer) .AndShould().NotDependOnAny(InfrastructureLayer) .AndShould().NotDependOnAny(PresentationLayer) .Check(Architecture); } [Fact] public void Application_Should_Only_Depend_On_Domain() { Types() .That().Are(ApplicationLayer) .Should().NotDependOnAny(InfrastructureLayer) .AndShould().NotDependOnAny(PresentationLayer) .Check(Architecture); } [Fact] public void Controllers_Should_Follow_Naming_Convention() { Types() .That().Are(PresentationLayer) .And().AreAssignableTo(typeof(Microsoft.AspNetCore.Mvc.ControllerBase)) .Should().HaveNameEndingWith("Controller") .Check(Architecture); } [Fact] public void Services_Should_Be_Internal() { Types() .That().HaveNameEndingWith("Service") .And().AreClasses() .And().DoNotHaveNameStartingWith("I") .Should().BeInternal() .Check(Architecture); } } ``` -
patterns.md 5 KB
# ArchUnitNET Rule Patterns ## Architecture Loading Load the architecture once per test class to avoid repeated assembly scanning: ```csharp private static readonly Architecture Architecture = new ArchLoader() .LoadAssemblies(typeof(SomeClassInTargetAssembly).Assembly) .Build(); ``` For multiple assemblies: ```csharp private static readonly Architecture Architecture = new ArchLoader() .LoadAssemblies( typeof(Domain.Entity).Assembly, typeof(Application.Service).Assembly, typeof(Infrastructure.Repository).Assembly) .Build(); ``` ## Rule Syntax Patterns ### Basic Pattern ```csharp IArchRule rule = Types() .That() .Are(predicate) .Should() .Be(condition); rule.Check(Architecture); ``` ### With Test Framework Integration Using xUnit: ```csharp [Fact] public void Controllers_Should_Not_Depend_On_Repositories() { Types() .That().ResideInNamespace("Controllers") .Should().NotDependOnAny(Types().That().ResideInNamespace("Repositories")) .Check(Architecture); } ``` Using MSTest: ```csharp [TestMethod] public void Controllers_Should_Not_Depend_On_Repositories() { Types() .That().ResideInNamespace("Controllers") .Should().NotDependOnAny(Types().That().ResideInNamespace("Repositories")) .Check(Architecture); } ``` ## Selection Patterns ### By Namespace ```csharp Types().That().ResideInNamespace("MyApp.Domain") Types().That().ResideInNamespaceContaining("Services") Types().That().ResideInNamespaceMatching(@"MyApp\..*\.Handlers") ``` ### By Naming Convention ```csharp Types().That().HaveNameEndingWith("Controller") Types().That().HaveNameStartingWith("I") Types().That().HaveNameMatching(@".*Handler$") ``` ### By Type Characteristics ```csharp Types().That().AreClasses() Types().That().AreInterfaces() Types().That().AreAbstract() Types().That().AreSealed() Types().That().ArePublic() Types().That().ImplementInterface(typeof(IService)) ``` ### By Inheritance ```csharp Types().That().AreAssignableTo(typeof(BaseClass)) Types().That().ImplementInterface(typeof(IHandler<>)) ``` ### By Attributes ```csharp Types().That().HaveAnyAttributes(typeof(SerializableAttribute)) Types().That().DoNotHaveAnyAttributes(typeof(ObsoleteAttribute)) ``` ## Condition Patterns ### Dependency Rules ```csharp .Should().NotDependOnAny(Types().That().ResideInNamespace("Infrastructure")) .Should().OnlyDependOn(Types().That().ResideInNamespace("Domain")) .Should().DependOnAny(Types().That().ResideInNamespace("Contracts")) ``` ### Namespace Residence ```csharp .Should().ResideInNamespace("MyApp.Application") .Should().ResideInNamespaceContaining("Handlers") ``` ### Naming Requirements ```csharp .Should().HaveNameEndingWith("Service") .Should().HaveNameStartingWith("I") ``` ### Visibility Requirements ```csharp .Should().BePublic() .Should().BeInternal() .Should().BeSealed() ``` ### Inheritance Requirements ```csharp .Should().ImplementInterface(typeof(IDisposable)) .Should().BeAssignableTo(typeof(BaseEntity)) ``` ## Layer Definition Pattern Define layers as reusable predicates: ```csharp private static readonly IObjectProvider<IType> DomainLayer = Types().That().ResideInNamespace("MyApp.Domain").As("Domain Layer"); private static readonly IObjectProvider<IType> ApplicationLayer = Types().That().ResideInNamespace("MyApp.Application").As("Application Layer"); private static readonly IObjectProvider<IType> InfrastructureLayer = Types().That().ResideInNamespace("MyApp.Infrastructure").As("Infrastructure Layer"); private static readonly IObjectProvider<IType> PresentationLayer = Types().That().ResideInNamespace("MyApp.Web").As("Presentation Layer"); ``` Use layers in rules: ```csharp [Fact] public void Domain_Should_Not_Depend_On_Application() { Types().That().Are(DomainLayer) .Should().NotDependOnAny(ApplicationLayer) .Check(Architecture); } ``` ## Combining Rules Combine multiple conditions: ```csharp Types() .That().ResideInNamespace("MyApp.Domain") .And().AreClasses() .And().AreNotAbstract() .Should().BeSealed() .OrShould().ImplementInterface(typeof(IEntity)) .Check(Architecture); ``` ## Custom Predicates Create reusable custom predicates: ```csharp public static class CustomPredicates { public static IPredicate<IType> AreAggregateRoots() { return new SimplePredicate<IType>( type => type.ImplementsInterface(typeof(IAggregateRoot)), "are aggregate roots"); } } ``` Use in rules: ```csharp Types().That().Are(CustomPredicates.AreAggregateRoots()) .Should().ResideInNamespace("MyApp.Domain.Aggregates") .Check(Architecture); ``` ## Slice Pattern for Vertical Slices Check that vertical slices do not depend on each other: ```csharp SliceRuleDefinition.ForFunctions() .Should().NotDependOnEachOther() .Check(Architecture); ``` ## Cycle Detection Pattern Check for circular dependencies: ```csharp Types().That().ResideInNamespace("MyApp") .Should().BeFreeOfCycles() .Check(Architecture); ```
-
-
SKILL.md 3.9 KB
--- name: dotnet-archunitnet version: "1.0.0" category: "Architecture" description: "Use the open-source free `ArchUnitNET` library for architecture rules in .NET tests. Use when a repo needs richer architecture assertions than lightweight fluent rule libraries usually provide." compatibility: "Requires a .NET test project; supports dedicated integrations for xUnit, xUnit v3, MSTest, TUnit, and others where available." --- # ArchUnitNET for .NET ## Trigger On - the repo uses or wants `ArchUnitNET` - architecture testing needs richer modeling than simple dependency checks ## Value - produce a concrete project delta: code, docs, config, tests, CI, or review artifact - reduce ambiguity through explicit planning, verification, and final validation skills - leave reusable project context so future tasks are faster and safer ## Do Not Use For - the lightest possible architecture rule checks ## Inputs - the nearest `AGENTS.md` - target assemblies - architecture boundaries and naming conventions ## Quick Start 1. Read the nearest `AGENTS.md` and confirm scope and constraints. 2. Run this skill's `Workflow` through the `Ralph Loop` until outcomes are acceptable. 3. Return the `Required Result Format` with concrete artifacts and verification evidence. ## Workflow 1. Load the architecture once per test assembly where possible. 2. Encode a small number of durable, high-value architecture rules first. 3. Use the test-framework-specific integration package that matches the repo. ## Bootstrap When Missing If `ArchUnitNET` is not configured yet: 1. Detect existing setup: - `rg -n "TngTech\\.ArchUnitNET" -g '*.csproj' .` 2. Add packages to the architecture test project: - `dotnet add TEST_PROJECT.csproj package TngTech.ArchUnitNET` - add one framework bridge package: `TngTech.ArchUnitNET.xUnit`, `TngTech.ArchUnitNET.xUnitV3`, `TngTech.ArchUnitNET.MSTestV2`, or `TngTech.ArchUnitNET.TUnit` 3. Add at least one durable boundary rule test. 4. Wire architecture tests into the standard `test` command in `AGENTS.md` and CI. 5. Run `dotnet test TEST_PROJECT.csproj` and return `status: configured` or `status: improved`. 6. If `NetArchTest` already covers the same boundary policy and no gap exists, return `status: not_applicable`. ## Deliver - architecture tests with richer domain and type modeling - architecture-rule commands wired into repo test flow and CI expectations ## Validate - architecture load cost is reasonable for the suite - rules are stable and tied to real boundaries ## Ralph Loop Use the Ralph Loop for every task, including docs, architecture, testing, and tooling work. 1. Plan first (mandatory): - analyze current state - define target outcome, constraints, and risks - write a detailed execution plan - list final validation skills to run at the end, with order and reason 2. Execute one planned step and produce a concrete delta. 3. Review the result and capture findings with actionable next fixes. 4. Apply fixes in small batches and rerun the relevant checks or review steps. 5. Update the plan after each iteration. 6. Repeat until outcomes are acceptable or only explicit exceptions remain. 7. If a dependency is missing, bootstrap it or return `status: not_applicable` with explicit reason and fallback path. ### Required Result Format - `status`: `complete` | `clean` | `improved` | `configured` | `not_applicable` | `blocked` - `plan`: concise plan and current iteration step - `actions_taken`: concrete changes made - `validation_skills`: final skills run, or skipped with reasons - `verification`: commands, checks, or review evidence summary - `remaining`: top unresolved items or `none` For setup-only requests with no execution, return `status: configured` and exact next commands. ## Load References - `references/archunitnet.md` - `references/patterns.md` - `references/examples.md` ## Example Requests - "Use ArchUnitNET for layered architecture tests." - "Set up ArchUnitNET with xUnit or MSTest."
Comments (0)
Sign in to join the conversation.
Reviews (0)
No reviews yet.
No comments yet.