Purview.Architecture.Testing
1.1.1
dotnet add package Purview.Architecture.Testing --version 1.1.1
NuGet\Install-Package Purview.Architecture.Testing -Version 1.1.1
<PackageReference Include="Purview.Architecture.Testing" Version="1.1.1" />
<PackageVersion Include="Purview.Architecture.Testing" Version="1.1.1" />
<PackageReference Include="Purview.Architecture.Testing" />
paket add Purview.Architecture.Testing --version 1.1.1
#r "nuget: Purview.Architecture.Testing, 1.1.1"
#:package Purview.Architecture.Testing@1.1.1
#addin nuget:?package=Purview.Architecture.Testing&version=1.1.1
#tool nuget:?package=Purview.Architecture.Testing&version=1.1.1
Purview.Architecture.Testing
Enforces a repository's project and assembly dependency policy as TUnit tests, so the rules are defined once rather than copied into every repository's test project.
Install
dotnet add package Purview.Architecture.Testing
Adopt
Two things. A policy at .config/architecture-policy.json:
{
"$schema": "https://raw.githubusercontent.com/purview-dev/architests/main/architecture-policy.schema.json",
"version": 2,
"sourceRoots": ["src"],
"responsibilities": [
{
"name": "Core",
"aliases": ["Domain"],
"category": "domain",
"purpose": "The domain model, its invariants and its rules.",
"mayDependOn": [],
"mustNotDependOn": ["Application", "Persistence"],
"forbiddenFrameworkReferences": ["Microsoft.AspNetCore.App"]
}
]
}
And one derived test class:
[InheritsTests]
public sealed class ArchitectureTests : PurviewArchitectureTests;
[InheritsTests]is required. TUnit does not walk base types during discovery, so without it the class compiles, the run is green and nothing is enforced. TUnit's analyzer reportsTUnit0030— treat it as an error.
The rules
| Rule | Catches |
|---|---|
graphDiscovery |
An empty graph, which satisfies every other rule |
naming |
A project name matching no responsibility or alias |
discouragedName |
A project named after a category, so nothing can be kept out of it |
dependencyMatrix |
A declared ProjectReference the matrix forbids or does not permit |
assemblyDependencyMatrix |
A type in one responsibility using a type in another, including a dependency that arrives transitively or through a package |
siblingIndependence |
A qualified implementation referencing a qualified sibling |
compositionBoundary |
A non-host project referencing the composition root (category == "composition") |
forbiddenPackage |
A package matching a forbidden prefix for that responsibility |
forbiddenFramework |
A <FrameworkReference> matching a forbidden prefix, e.g. Microsoft.AspNetCore.App in a portable layer |
cycle |
A dependency cycle, reported readably |
testReference |
Production code referencing a test project |
waiverIntegrity |
A waiver that is stale, unjustified, malformed, or excuses an unknown rule |
The policy is validated against its schema first, because a policy that does not match its schema may be silently missing a rule.
The type-level rule
A project file only shows a declared ProjectReference. assemblyDependencyMatrix reads the
compiled assemblies by metadata only — no assembly is loaded or executed — and checks every external
type each one references against the same mayDependOn / mustNotDependOn matrix, so a dependency
that only arrives transitively or through a package is caught.
"assemblyRules": {
"enabled": true,
"assemblyRoots": [],
"assemblyGlobs": ["*.dll"]
}
- Off by default, because it needs a build to have produced the assemblies.
- An empty
assemblyRootsscans the test output directory, which already holds every project the test project references, directly or transitively. - Enabled with nothing found is a failure, not a silent pass.
- Semantics mirror
dependencyMatrix: same-responsibility references are always allowed, and a reference the matrix neither permits nor forbids is a violation. Qualified implementations collapse into their responsibility at the type level too.
Test and production classification
- A project is a test project when its name ends with
Tests, or its path contains atestsdirectory. Test projects are excluded from every rule. - Everything else is a production project, and every rule applies to it.
Waivers over skips
"waivers": [
{
"project": "Legacy.Utilities",
"mapsTo": "Shared",
"rules": ["naming"],
"reason": "Predates the catalogue; renaming breaks a published package id.",
"reviewedOn": "2026-01-14"
}
]
SkipRule removes a rule for the whole repository with no record. A waiver is per project, carries
a reason and a review date, keeps every other rule in force, and is itself checked. mapsTo maps an
established project onto the responsibility it actually has, so only the name is excused.
Any rule id may be waived. An unknown id is a waiverIntegrity failure, so a typo cannot silently
excuse nothing.
Adding your own rule
Implement IArchitectureRule and return the same ArchitectureViolation type as the built-in
rules, then list it in AdditionalRules:
[InheritsTests]
public sealed class ArchitectureTests : PurviewArchitectureTests
{
protected override IReadOnlyList<IArchitectureRule> AdditionalRules => [new NoConsoleLoggingRule()];
}
Each additional rule becomes its own [Test] and participates in the same failure reporting,
SkipRule and waiver model.
Sample and documentation
- A fully working sample repository:
src/samples/ArchitectureSample. - Guides: getting started, policy reference, type-level rules, extending.
Design
Three layers, deliberately separated: ArchitecturePolicy/ProjectGraph/AssemblyGraph read the
policy, the .csproj graph and compiled assembly metadata with no MSBuild or test-framework types;
ArchitecturePolicyEvaluator holds the rules and takes an in-memory graph, so a rule is testable as
a three-project fixture; PurviewArchitectureTests is the TUnit wrapper with one [Test] per rule.
Project files are read because a declared ProjectReference is what a reviewer adds and what the
policy governs; compiled assemblies are read separately because they catch what the project file
does not show.
Policy schema version
The current document version is 2. Version 2 removed the rules object, whose flags were
deserialised but never consulted; use SkipRule or a waiver to opt out of a rule. A version-1
policy that set rules must delete the block; one that did not still validates.
Part of Purview.BuildSdk · purview.dev
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net10.0
- JsonSchema.Net (>= 9.4.1)
- TUnit.Assertions (>= 1.75.0)
- TUnit.Core (>= 1.75.0)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.