
Netarchtest
- 18 installs
- 466 repo stars
- Updated July 25, 2026
- managedcode/dotnet-skills
Helps with testing & qa tasks.
About
netarchtest is a Claude Code skill for testing & qa. It helps solo builders move faster with AI-assisted coding.
- netarchtest
- Testing & QA
- AI-coding skill
Netarchtest by the numbers
- 18 all-time installs (skills.sh)
- +1 installs in the week ending Aug 2, 2026 (Skillselion tracking)
- Ranked #1,452 of 2,153 Testing & QA skills by installs in the Skillselion catalog
- Data as of Aug 3, 2026 (Skillselion catalog sync)
npx skills add https://github.com/managedcode/dotnet-skills --skill netarchtestAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 18 |
|---|---|
| repo stars | ★ 466 |
| Last updated | July 25, 2026 |
| Repository | managedcode/dotnet-skills ↗ |
What it does
Helps with testing & qa tasks.
Files
NetArchTest
Trigger On
- the repo uses or wants
NetArchTest.Rules - architecture rules should be enforced in automated tests
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
- very rich architecture modeling that needs a heavier DSL
Inputs
- the nearest
AGENTS.md - architecture boundaries to enforce
- target assemblies
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. Encode only durable architecture rules:
- forbidden dependencies
- namespace layering
- type shape conventions
2. Keep rules readable and close to the boundary they protect. 3. Fail tests on architecture drift, not on temporary style noise.
Bootstrap When Missing
If NetArchTest.Rules is not configured yet:
1. Detect existing setup:
rg -n "NetArchTest\\.Rules" -g '*.csproj' .
2. Add the package to the architecture test project:
dotnet add TEST_PROJECT.csproj package NetArchTest.Rules
3. Add at least one executable 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 richer modeling is required and ArchUnitNET is chosen as the standard, return status: not_applicable.
Deliver
- architecture tests that are understandable and stable
- boundary checks wired into the normal test path used by agents and CI
Validate
- the rules map to real boundaries the team cares about
- failures point to actionable dependency drift
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|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/patterns.md
- references/examples.md
- references/netarchtest.md
Example Requests
- "Add architecture tests with NetArchTest."
- "Block UI from referencing data directly."
{
"version": "1.0.0",
"category": "Architecture",
"packages": [
"NetArchTest.Rules",
"NetArchTest.eNhancedEdition"
]
}
NetArchTest Common Architecture Rules
This reference provides ready-to-use architecture test examples for common .NET project structures.
Clean Architecture / Onion Architecture
Domain Layer Independence
The domain layer should have no dependencies on infrastructure or application layers:
[Fact]
public void Domain_ShouldNotDependOn_Infrastructure()
{
var result = Types
.InAssembly(typeof(DomainAssemblyMarker).Assembly)
.ShouldNot()
.HaveDependencyOn("MyApp.Infrastructure")
.GetResult();
Assert.True(result.IsSuccessful,
$"Domain depends on Infrastructure: {FormatFailures(result)}");
}
[Fact]
public void Domain_ShouldNotDependOn_Application()
{
var result = Types
.InAssembly(typeof(DomainAssemblyMarker).Assembly)
.ShouldNot()
.HaveDependencyOn("MyApp.Application")
.GetResult();
Assert.True(result.IsSuccessful,
$"Domain depends on Application: {FormatFailures(result)}");
}Application Layer Allowed Dependencies
[Fact]
public void Application_ShouldOnlyDependOn_AllowedLayers()
{
var result = Types
.InAssembly(typeof(ApplicationAssemblyMarker).Assembly)
.That()
.ResideInNamespace("MyApp.Application")
.ShouldNot()
.HaveDependencyOnAny(
"MyApp.Infrastructure",
"MyApp.Web",
"MyApp.Api")
.GetResult();
Assert.True(result.IsSuccessful,
$"Application layer violation: {FormatFailures(result)}");
}Infrastructure Implements Domain Interfaces
[Fact]
public void Repositories_ShouldImplement_DomainInterfaces()
{
var result = Types
.InAssembly(typeof(InfrastructureAssemblyMarker).Assembly)
.That()
.HaveNameEndingWith("Repository")
.Should()
.ImplementInterface(typeof(IRepository<>))
.GetResult();
Assert.True(result.IsSuccessful,
$"Repositories missing interface: {FormatFailures(result)}");
}Layered Architecture (N-Tier)
Layer Dependency Direction
[Fact]
public void DataLayer_ShouldNotDependOn_BusinessLayer()
{
var result = Types
.InAssembly(typeof(DataLayerMarker).Assembly)
.ShouldNot()
.HaveDependencyOn("MyApp.Business")
.GetResult();
Assert.True(result.IsSuccessful);
}
[Fact]
public void BusinessLayer_ShouldNotDependOn_PresentationLayer()
{
var result = Types
.InAssembly(typeof(BusinessLayerMarker).Assembly)
.ShouldNot()
.HaveDependencyOn("MyApp.Web")
.GetResult();
Assert.True(result.IsSuccessful);
}CQRS Pattern
Command Handlers Location
[Fact]
public void CommandHandlers_ShouldResideIn_ApplicationLayer()
{
var result = Types
.InCurrentDomain()
.That()
.HaveNameEndingWith("CommandHandler")
.Should()
.ResideInNamespaceStartingWith("MyApp.Application")
.GetResult();
Assert.True(result.IsSuccessful);
}Commands Should Be Immutable
[Fact]
public void Commands_ShouldBe_Sealed()
{
var result = Types
.InAssembly(typeof(ApplicationAssemblyMarker).Assembly)
.That()
.ImplementInterface(typeof(ICommand))
.Should()
.BeSealed()
.GetResult();
Assert.True(result.IsSuccessful);
}Queries Should Not Modify State
[Fact]
public void QueryHandlers_ShouldNotDependOn_WriteRepositories()
{
var result = Types
.InAssembly(typeof(ApplicationAssemblyMarker).Assembly)
.That()
.HaveNameEndingWith("QueryHandler")
.ShouldNot()
.HaveDependencyOnAny(
"MyApp.Infrastructure.Persistence.Write",
"MyApp.Application.Commands")
.GetResult();
Assert.True(result.IsSuccessful);
}Domain-Driven Design
Aggregates Encapsulation
[Fact]
public void Entities_ShouldNotExpose_PublicSetters()
{
// Use custom rule for property analysis
var result = Types
.InAssembly(typeof(DomainAssemblyMarker).Assembly)
.That()
.Inherit(typeof(Entity))
.And()
.AreNotAbstract()
.Should()
.MeetCustomRule(new NoPublicSettersRule())
.GetResult();
Assert.True(result.IsSuccessful);
}Value Objects Are Immutable
[Fact]
public void ValueObjects_ShouldBe_Sealed()
{
var result = Types
.InAssembly(typeof(DomainAssemblyMarker).Assembly)
.That()
.Inherit(typeof(ValueObject))
.Should()
.BeSealed()
.GetResult();
Assert.True(result.IsSuccessful);
}Domain Events Location
[Fact]
public void DomainEvents_ShouldResideIn_DomainLayer()
{
var result = Types
.InCurrentDomain()
.That()
.ImplementInterface(typeof(IDomainEvent))
.Should()
.ResideInNamespaceStartingWith("MyApp.Domain")
.GetResult();
Assert.True(result.IsSuccessful);
}API / Web Layer
Controllers Naming Convention
[Fact]
public void Controllers_ShouldHave_CorrectSuffix()
{
var result = Types
.InAssembly(typeof(WebAssemblyMarker).Assembly)
.That()
.Inherit(typeof(ControllerBase))
.Should()
.HaveNameEndingWith("Controller")
.GetResult();
Assert.True(result.IsSuccessful);
}Controllers Should Be In Controllers Namespace
[Fact]
public void Controllers_ShouldResideIn_ControllersNamespace()
{
var result = Types
.InAssembly(typeof(WebAssemblyMarker).Assembly)
.That()
.Inherit(typeof(ControllerBase))
.Should()
.ResideInNamespaceContaining("Controllers")
.GetResult();
Assert.True(result.IsSuccessful);
}Service Registration
Services Should Have Interfaces
[Fact]
public void Services_ShouldImplement_Interface()
{
var result = Types
.InAssembly(typeof(ApplicationAssemblyMarker).Assembly)
.That()
.HaveNameEndingWith("Service")
.And()
.AreClasses()
.Should()
.ImplementInterface(typeof(object)) // Any interface
.GetResult();
// Better: check for matching I{Name} interface pattern
var services = Types
.InAssembly(typeof(ApplicationAssemblyMarker).Assembly)
.That()
.HaveNameEndingWith("Service")
.And()
.AreClasses()
.GetTypes();
foreach (var service in services)
{
var expectedInterface = $"I{service.Name}";
Assert.True(
service.GetInterfaces().Any(i => i.Name == expectedInterface),
$"{service.Name} should implement {expectedInterface}");
}
}No Circular Dependencies
Namespace Slices Should Not Have Cycles
[Fact]
public void Namespaces_ShouldNotHave_CircularDependencies()
{
var slices = Types
.InAssembly(typeof(ApplicationAssemblyMarker).Assembly)
.Slice()
.ByNamespacePrefix("MyApp.Application.Features");
var result = slices.Should().NotHaveDependenciesBetweenSlices();
Assert.True(result.IsSuccessful);
}Utility Helpers
Format Failure Message
private static string FormatFailures(TestResult result)
{
if (result.FailingTypeNames == null || !result.FailingTypeNames.Any())
return "No details available";
return string.Join(Environment.NewLine,
result.FailingTypeNames.Select(t => $" - {t}"));
}Assembly Marker Pattern
Create empty marker interfaces in each project for easy assembly reference:
// In MyApp.Domain project
namespace MyApp.Domain;
public interface IDomainAssemblyMarker { }
// In tests
var domainAssembly = typeof(IDomainAssemblyMarker).Assembly;Test Organization
Recommended Test Class Structure
public class ArchitectureTests
{
private readonly Assembly _domainAssembly;
private readonly Assembly _applicationAssembly;
private readonly Assembly _infrastructureAssembly;
public ArchitectureTests()
{
_domainAssembly = typeof(DomainMarker).Assembly;
_applicationAssembly = typeof(ApplicationMarker).Assembly;
_infrastructureAssembly = typeof(InfrastructureMarker).Assembly;
}
[Fact]
public void DomainLayer_HasNoDependencyOn_OtherLayers()
{
var result = Types
.InAssembly(_domainAssembly)
.ShouldNot()
.HaveDependencyOnAny(
"MyApp.Application",
"MyApp.Infrastructure",
"MyApp.Web")
.GetResult();
Assert.True(result.IsSuccessful,
$"Domain layer violations:{Environment.NewLine}{FormatFailures(result)}");
}
}NetArchTest.Rules
Open/Free Status
- open source
- free to use
Install
dotnet add package NetArchTest.RulesVerify First
Before adding the package, check whether the repo already references it:
rg -n "NetArchTest\\.Rules" -g '*.csproj' .Common Usage
Use inside your test project:
var result = Types.InAssembly(typeof(MyType).Assembly)
.That().ResideInNamespace("MyApp.Presentation")
.ShouldNot().HaveDependencyOn("MyApp.Data")
.GetResult()
.IsSuccessful;CI Fit
- runs as part of the normal test suite
- good for clean architecture and layering rules
When Not To Use
- when the repo needs richer architecture modeling and custom assertions than NetArchTest provides comfortably
Sources
NetArchTest Rule Patterns
This reference covers the core fluent API patterns for NetArchTest.Rules.
Basic Pattern Structure
All NetArchTest rules follow a three-part fluent pattern:
var result = Types
.InAssembly(assembly) // 1. Select types
.That() // 2. Apply predicates
.ShouldNot().HaveDependencyOn("SomeNamespace") // 3. Assert condition
.GetResult();
Assert.True(result.IsSuccessful);Type Selection Patterns
From Assembly
// Current assembly
Types.InCurrentDomain()
// Specific assembly
Types.InAssembly(typeof(MyClass).Assembly)
// Multiple assemblies
Types.InAssemblies(assemblies)From Namespace
Types.InAssembly(assembly)
.That()
.ResideInNamespace("MyApp.Domain")Namespace Matching Variants
// Exact namespace
.ResideInNamespace("MyApp.Domain")
// Namespace prefix (includes sub-namespaces)
.ResideInNamespaceStartingWith("MyApp.Domain")
// Namespace suffix
.ResideInNamespaceEndingWith(".Handlers")
// Namespace contains
.ResideInNamespaceContaining("Services")Type Predicates
By Naming Convention
.HaveNameStartingWith("I") // Interfaces
.HaveNameEndingWith("Service") // Services
.HaveNameMatching(".*Handler$") // Regex matchBy Inheritance
.Inherit(typeof(BaseClass))
.ImplementInterface(typeof(IService))
.BeAssignableTo(typeof(ICommand))By Attributes
.HaveCustomAttribute(typeof(SerializableAttribute))
.HaveCustomAttributeOrInherit(typeof(MyAttribute))By Type Kind
.BeClasses()
.BeInterfaces()
.BeSealed()
.BeAbstract()
.BePublic()
.BeInternal()
.BeStatic()
.BeGeneric()By Dependencies
.HaveDependencyOn("System.Data")
.HaveDependencyOnAny("Lib1", "Lib2")
.HaveDependencyOnAll("Required1", "Required2")
.OnlyHaveDependenciesOn("Allowed1", "Allowed2")Assertion Patterns
Positive Assertions
.Should().BePublic()
.Should().BeSealed()
.Should().HaveDependencyOn("Required.Namespace")
.Should().ImplementInterface(typeof(IRequired))Negative Assertions
.ShouldNot().BePublic()
.ShouldNot().HaveDependencyOn("Forbidden.Namespace")
.ShouldNot().HaveDependencyOnAny("Bad1", "Bad2")Combining Predicates
And (implicit)
Chained predicates are ANDed:
.That()
.ResideInNamespace("MyApp.Services")
.And()
.HaveNameEndingWith("Service")Or
.That()
.ResideInNamespace("MyApp.Services")
.Or()
.ResideInNamespace("MyApp.Handlers")Result Handling
Basic Result Check
var result = Types.InAssembly(assembly)
.That()
.ResideInNamespace("MyApp.Domain")
.ShouldNot().HaveDependencyOn("MyApp.Infrastructure")
.GetResult();
Assert.True(result.IsSuccessful,
$"Domain layer violation: {string.Join(", ", result.FailingTypeNames ?? Array.Empty<string>())}");Detailed Failure Inspection
if (!result.IsSuccessful)
{
foreach (var failingType in result.FailingTypes)
{
Console.WriteLine($"Violation: {failingType.FullName}");
}
}Custom Predicates
Using MeetCustomRule
.MeetCustomRule(new MyCustomRule())Implementing ICustomRule
public class NoPublicFieldsRule : ICustomRule
{
public bool MeetsRule(TypeDefinition type)
{
return !type.Fields.Any(f => f.IsPublic && !f.IsStatic);
}
}Slices Pattern (Dependency Cycles)
var slices = Types.InAssembly(assembly)
.Slice()
.ByNamespacePrefix("MyApp");
slices.Should().NotHaveDependenciesBetweenSlices();Excluding Types
Types.InAssembly(assembly)
.That()
.ResideInNamespace("MyApp.Domain")
.And()
.DoNotHaveNameMatching(".*Tests$") // Exclude test types
.And()
.AreNotNested() // Exclude nested typesAssembly-Level Rules
// Check assembly references
var assembly = typeof(MyClass).Assembly;
var references = assembly.GetReferencedAssemblies();
// Ensure no reference to forbidden assembly
Assert.DoesNotContain(references, r => r.Name == "ForbiddenAssembly");