![]() |
VOOZH | about |
dotnet add package NetArchTest.eNhancedEdition --version 1.4.5
NuGet\Install-Package NetArchTest.eNhancedEdition -Version 1.4.5
<PackageReference Include="NetArchTest.eNhancedEdition" Version="1.4.5" />
<PackageVersion Include="NetArchTest.eNhancedEdition" Version="1.4.5" />Directory.Packages.props
<PackageReference Include="NetArchTest.eNhancedEdition" />Project file
paket add NetArchTest.eNhancedEdition --version 1.4.5
#r "nuget: NetArchTest.eNhancedEdition, 1.4.5"
#:package NetArchTest.eNhancedEdition@1.4.5
#addin nuget:?package=NetArchTest.eNhancedEdition&version=1.4.5Install as a Cake Addin
#tool nuget:?package=NetArchTest.eNhancedEdition&version=1.4.5Install as a Cake Tool
A fluent API for .Net Standard that can enforce architectural rules in unit tests and create a self-testing architecture. Inspired by the ArchUnit library for Java.
NetArchTest.eNhancedEdition is based on NetArchTest v1.3.2. If you are not familiar with NetArchTest, you should start by reading introduction on Ben's blog.
NetArchTest is a well-established mature library, but to push things forward, a few breaking changes had to be made, and that is how eNhancedEdition was born. eNhancedEdition uses almost identical Fluent API as a base library, but it is not 100% backwards compatible, and it will never be.
What eNhancedEdition has to offer, that is not available in the NetArchTest v1.3.2:
👁 revit-database-scripting-update-query
The library is available as a package on NuGet: NetArchTest.eNhancedEdition.
[TestClass]
public class SampleApp_ModuleAlpha_Tests
{
static readonly Assembly AssemblyUnderTest = typeof(TestUtils).Assembly;
[TestMethod]
public void PersistenceIsNotAccessibleFromOutsideOfModuleExceptOfDbContext()
{
var result = Types.InAssembly(AssemblyUnderTest)
.That()
.ResideInNamespace("SampleApp.ModuleAlpha.Persistence")
.And()
.DoNotHaveNameEndingWith("DbContext")
.Should()
.NotBePublic()
.GetResult();
Assert.IsTrue(result.IsSuccessful);
}
[TestMethod]
public void DomainIsIndependent()
{
var result = Types.InAssembly(AssemblyUnderTest)
.That()
.ResideInNamespace("SampleApp.ModuleAlpha.Domain")
.ShouldNot()
.HaveDependencyOtherThan(
"System",
"SampleApp.ModuleAlpha.Domain",
"SampleApp.SharedKernel.Domain",
"SampleApp.BuildingBlocks.Domain"
)
.GetResult();
Assert.IsTrue(result.IsSuccessful, "Domain has lost its independence!");
}
}
[TestClass]
public class SampleApp_ModuleOmega_Tests
{
static readonly Assembly AssemblyUnderTest = typeof(TestUtils).Assembly;
[TestMethod]
public void RequestHandlersShouldBeSealed()
{
var result = Types.InAssembly(AssemblyUnderTest)
.That()
.ImplementInterface(typeof(IRequestHandler<,>))
.Should()
.BeSealed()
.GetResult();
Assert.IsTrue(result.IsSuccessful);
}
}
The fluent API should direct you in building up a rule, based on a combination of , and conjunctions.
The starting point for any rule is one of the static methods on class, where you load a set of types from an assembly, domain or path.
var types = Types.InAssembly(typeof(MyClass).Assembly);
Once you have loaded the types, you can filter them using one or more predicates. These can be chained together using And() or Or() conjunctions:
types.That().ResideInNamespace("MyProject.Data");
Once the set of types has been filtered, you can apply a set of conditions using the Should() or ShouldNot() methods, e.g.
types.That().ResideInNamespace("MyProject.Data").Should().BeSealed();
Finally, you obtain a result from the rule by using an executor, i.e. use GetTypes() to return the types that match the rule or GetResult() to determine whether the rule has been met.
Note that GetResult() returns which contains a few lists of types:
LoadedTypes - all types loaded by SelectedTypesForTesting - types that passed FailingTypes- types that failed to meet the var result = types.That().ResideInNamespace("MyProject.Data").Should().BeSealed().GetResult();
var isValid = result.IsSuccessful;
var types = result.FailingTypes;
Tip Loading types is time-consuming, since
Typeclass is immutable, its instance can be shared between tests.
Dependency matrix:
| type\has dependency on | D1 | D2 | D3 |
|---|---|---|---|
| a | |||
| b | x | ||
| c | x | ||
| d | x | x | |
| e | x | ||
| f | x | x | |
| g | x | x | |
| h | x | x | x |
| Rule | number<br> of required<br> dependencies <br>from the list | type can have<br>a dependency<br>that is not<br>on the list | passing types | failing types | |
|---|---|---|---|---|---|
| 1 | at least 1 | yes | c, d, e, f, g, h, | a, b | |
| 2 | all | yes | g, h | a, b, c, d, e, f | |
| 3 | >=0 | no | a, c, e, g | b, d, f, h | |
| 1N | none | yes | a, b | c, d, e, f, g, h, | |
| 2N | not all | yes | a, b, c, d, e, f | g, h | |
| 3N | >=0 | yes | b, d, f, h, | a, c, e, g |
An explanation of why a type fails the dependency search test is available on the failing type:
| Predicate | number<br> of required<br> dependencies <br>from the list | type can use<br>a type<br>that is not<br>on the list | passing types | failing types | |
|---|---|---|---|---|---|
| R1 | at least 1 | yes | D2, D3 | D1 | |
| R1N | none | yes | D1 | D2, D3 |
A Type is considered as immutable when all its state (instance and static, fields, properties and events) cannot be changed after creation. Shallow immutability.
A Type is considered as externally immutable when its state (instance and static, fields, properties and events) with a public access modifier cannot be changed from the outside of the type. Shallow immutability.
A Type is considered as stateless when it does not have an instance state (fields, properties and events).
A Type is considered as stateless when it does not have a static state.
A type should have a parameterless instance constructor.
A type should not have any instance public constructors.
var result = Types.InAssembly(typeof(ExampleDependency).Assembly)
.Slice()
.ByNamespacePrefix("MyApp.Features")
.Should()
.NotHaveDependenciesBetweenSlices()
.GetResult();
There is only one way, at least for now, to divide types into slices ByNamespacePrefix(string prefix) and it works as follows:
namespacePrefix.(sliceName).restOfNamespacesliceName part will be placed in the same slice. If sliceName is empty for a given type, the type will also be ignored (BaseFeature class from the following image)When our types are divided into slices, we can apply the condition: NotHaveDependenciesBetweenSlices(). As the name suggests it detects if any dependency exists between slices. Dependency from slice to type that is not part of any other slice is allowed.
| passing | failing |
|---|---|
| 👁 Slices |
👁 Slices |
You can extend the library by writing custom rules that implement the ICustomRule interface. These can be applied as both predicates and conditions using a MeetsCustomRule() method, e.g.
var myRule = new CustomRule();
// Write your own custom rules that can be used as both predicates and conditions
var result = Types.InCurrentDomain()
.That()
.AreClasses()
.Should()
.MeetCustomRule(myRule)
.GetResult()
.IsSuccessful;
User allows to configure how NetArchTest engine works.
var result = Types.InCurrentDomain()
.That()
.ResideInNamespace("NetArchTest.TestStructure.NameMatching.Namespace3")
.Should()
.HaveNameStartingWith("Some")
.GetResult(Options.Default with { Comparer = StringComparison.Ordinal});
Assert.True(result.IsSuccessful);
Available options:
- allows to specify how strings will be compared, default: InvariantCultureIgnoreCase (it only affects: Predicate.HaveName, Predicate.HaveNameStartingWith, Predicate.HaveNameEndingWith, Predicate.ResideInNamespace)
- determines if dependency analysis should look for dependency in string field constant, default: false
NetArchTest is built on top of jbevain/cecil thus it works on CIL level. Unfortunately, not every feature of C# language is represented in CIL, thus some things will never be available in NetArchTest, e.g.:
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net5.0 net5.0 was computed. net5.0-windows net5.0-windows was computed. net6.0 net6.0 was computed. net6.0-android net6.0-android was computed. net6.0-ios net6.0-ios was computed. net6.0-maccatalyst net6.0-maccatalyst was computed. net6.0-macos net6.0-macos was computed. net6.0-tvos net6.0-tvos was computed. net6.0-windows net6.0-windows was computed. net7.0 net7.0 was computed. net7.0-android net7.0-android was computed. net7.0-ios net7.0-ios was computed. net7.0-maccatalyst net7.0-maccatalyst was computed. net7.0-macos net7.0-macos was computed. net7.0-tvos net7.0-tvos was computed. net7.0-windows net7.0-windows was computed. net8.0 net8.0 was computed. net8.0-android net8.0-android was computed. net8.0-browser net8.0-browser was computed. net8.0-ios net8.0-ios was computed. net8.0-maccatalyst net8.0-maccatalyst was computed. net8.0-macos net8.0-macos was computed. net8.0-tvos net8.0-tvos was computed. net8.0-windows net8.0-windows was computed. net9.0 net9.0 was computed. net9.0-android net9.0-android was computed. net9.0-browser net9.0-browser was computed. net9.0-ios net9.0-ios was computed. net9.0-maccatalyst net9.0-maccatalyst was computed. net9.0-macos net9.0-macos was computed. net9.0-tvos net9.0-tvos was computed. net9.0-windows net9.0-windows was computed. net10.0 net10.0 was computed. net10.0-android net10.0-android was computed. net10.0-browser net10.0-browser was computed. net10.0-ios net10.0-ios was computed. net10.0-maccatalyst net10.0-maccatalyst was computed. net10.0-macos net10.0-macos was computed. net10.0-tvos net10.0-tvos was computed. net10.0-windows net10.0-windows was computed. |
| .NET Core | netcoreapp2.0 netcoreapp2.0 was computed. netcoreapp2.1 netcoreapp2.1 was computed. netcoreapp2.2 netcoreapp2.2 was computed. netcoreapp3.0 netcoreapp3.0 was computed. netcoreapp3.1 netcoreapp3.1 was computed. |
| .NET Standard | netstandard2.0 netstandard2.0 is compatible. netstandard2.1 netstandard2.1 was computed. |
| .NET Framework | net461 net461 was computed. net462 net462 was computed. net463 net463 was computed. net47 net47 was computed. net471 net471 was computed. net472 net472 was computed. net48 net48 was computed. net481 net481 was computed. |
| MonoAndroid | monoandroid monoandroid was computed. |
| MonoMac | monomac monomac was computed. |
| MonoTouch | monotouch monotouch was computed. |
| Tizen | tizen40 tizen40 was computed. tizen60 tizen60 was computed. |
| Xamarin.iOS | xamarinios xamarinios was computed. |
| Xamarin.Mac | xamarinmac xamarinmac was computed. |
| Xamarin.TVOS | xamarintvos xamarintvos was computed. |
| Xamarin.WatchOS | xamarinwatchos xamarinwatchos was computed. |
Showing the top 1 NuGet packages that depend on NetArchTest.eNhancedEdition:
| Package | Downloads |
|---|---|
|
TimeWarp.State.Policies
Policies for TimeWarp State management library |
Showing the top 4 popular GitHub repositories that depend on NetArchTest.eNhancedEdition:
| Repository | Stars |
|---|---|
|
TimeWarpEngineering/timewarp-state
A Blazor State management library by TimeWarp.
|
|
|
lduchosal/ipnetwork
IPNetwork command line and C# library take care of complex network, IP, IPv4, IPv6, netmask, CIDR, subnet, subnetting, supernet, and supernetting calculation for .NET developers. It works with IPv4 as well as IPv6, is written in C#, has a light and clean API, and is fully unit-tested
|
|
|
AArnott/Nerdbank.MessagePack
A feature-packed .NET MessagePack serialization library with great performance and simplicity. msgpack.io[C#]
|
|
|
Euterpe-org/Euterpe
The community platform for Muse Dash custom charts, mods, and creator tooling.
|
| Version | Downloads | Last Updated |
|---|---|---|
| 1.4.5 | 258,399 | 6/4/2025 |
| 1.4.4 | 96,364 | 2/19/2025 |
| 1.4.3 | 251,810 | 3/16/2024 |
| 1.4.2 | 3,742 | 3/11/2024 |
| 1.4.1 | 42,133 | 12/6/2023 |
| 1.4.0 | 821 | 11/27/2023 |
| 1.3.9 | 342 | 11/25/2023 |
| 1.3.8 | 1,154 | 11/21/2023 |
| 1.3.7 | 6,225 | 8/4/2023 |
| 1.3.6 | 2,179 | 6/1/2023 |
| 1.3.5 | 443 | 5/13/2023 |
| 1.3.4 | 23,126 | 1/11/2023 |
| 1.3.3 | 7,710 | 10/4/2022 |