Resrcify.SharedKernel.ArchitectureTesting
4.7.0
dotnet add package Resrcify.SharedKernel.ArchitectureTesting --version 4.7.0
NuGet\Install-Package Resrcify.SharedKernel.ArchitectureTesting -Version 4.7.0
<PackageReference Include="Resrcify.SharedKernel.ArchitectureTesting" Version="4.7.0" />
<PackageVersion Include="Resrcify.SharedKernel.ArchitectureTesting" Version="4.7.0" />
<PackageReference Include="Resrcify.SharedKernel.ArchitectureTesting" />
paket add Resrcify.SharedKernel.ArchitectureTesting --version 4.7.0
#r "nuget: Resrcify.SharedKernel.ArchitectureTesting, 4.7.0"
#:package Resrcify.SharedKernel.ArchitectureTesting@4.7.0
#addin nuget:?package=Resrcify.SharedKernel.ArchitectureTesting&version=4.7.0
#tool nuget:?package=Resrcify.SharedKernel.ArchitectureTesting&version=4.7.0
Resrcify.SharedKernel.ArchitectureTesting
Architecture tests for a layered Clean Architecture service, ready to inherit. Subclass a base in the service's architecture-test project and xUnit runs its tests there; no rules to write.
What you get
| Base class | Checks |
|---|---|
ConventionalLayerDependencyTests |
Dependencies point inward: a layer never depends on a higher tier or on a peer (Domain 0, Application 1, Persistence 2, Infrastructure and Presentation 3, Web 4), Web never reaches Domain and Presentation never reaches Persistence. Also which SharedKernel packages each layer may use (a custom layer: what the layers at its tier may use). |
ConventionalDomainTests |
Domain events are sealed and end with Event; entities have a private constructor. |
ConventionalApplicationTests |
Commands, queries, requests and their handlers are sealed and named by convention (…Command, …CommandHandler, …Query, …QueryHandler, …Request, …RequestHandler); handlers return Task<Result>. In every layer, a request handler doesn't depend on ISender or IMediator (below). |
ConventionalConfigurationTests |
In every layer, no type takes IConfiguration in a constructor: services get validated options (IOptions<T>); configuration is read at registration (below). |
ConventionalPresentationTests |
Endpoints implement IEndpoint, are sealed, expose a single map method and go through the mediator; so do controllers. |
The service's layer assemblies are found by name: a test project called Titan.Shard.ArchitectureTests uses every
referenced assembly named Titan.Shard.<Layer>. A layer the service doesn't have is skipped.
Install
dotnet add package Resrcify.SharedKernel.ArchitectureTesting
Reference each layer project from the architecture-test project, so its assembly is found.
Use
One class per base:
public sealed class LayerTests : ConventionalLayerDependencyTests;
public sealed class DomainTests : ConventionalDomainTests;
public sealed class ApplicationTests : ConventionalApplicationTests;
public sealed class PresentationTests : ConventionalPresentationTests;
public sealed class ConfigurationTests : ConventionalConfigurationTests;
Every test is virtual; skip one the service deliberately breaks, with the reason:
[SkippableFact]
public override void Commands_Should_HaveCommandPostfix()
=> Skip.If(true, "This service names its commands after the RPC.");
Add a layer, or move one to another tier, by overriding LayerTiers; add rules the tiers can't express with
ExtraForbiddenDependencies:
using Resrcify.SharedKernel.ArchitectureTesting.Layering; // Layers
using Resrcify.SharedKernel.ArchitectureTesting.Tests;
public sealed class LayerTests : ConventionalLayerDependencyTests
{
protected override IReadOnlyDictionary<string, int> LayerTiers
=> new Dictionary<string, int>(base.LayerTiers) { ["Bot"] = 4 };
protected override IEnumerable<(string Source, string Target)> ExtraForbiddenDependencies
=> [.. base.ExtraForbiddenDependencies, ("Bot", Layers.Domain)];
}
A layer added this way also gets the SharedKernel package restrictions (SharedKernelPackageRestrictions, which name
only the conventional layers): those of the conventional layers at its tier. The Bot above, at tier 4, may use what
Web may use; a layer at tier 3 is restricted like Infrastructure and Presentation together; one at a tier without a
conventional layer, like the nearest tier's (the inner one on a tie). A failure says where the rule came from
(Bot → Repository: … (restricted like Web; …)). Choose the layer yourself with PackageRestrictionsLike:
protected override IReadOnlyDictionary<string, string> PackageRestrictionsLike
=> new Dictionary<string, string> { ["Client"] = Layers.Infrastructure };
A custom layer that an overridden SharedKernelPackageRestrictions already names is left as configured there (unless
PackageRestrictionsLike maps it), so an override that adds it by hand keeps working; it is no longer needed.
To check assemblies that aren't named <Root>.<Layer>, pass them to the base's constructor
(ConventionalLayerDependencyTests(IReadOnlyDictionary<string, Assembly> layerAssemblies), also on
ConventionalApplicationTests and BaseArchitectureTest).
Handlers don't send requests
ConventionalApplicationTests.Handlers_Should_NotDependOnSenderOrMediator fails for a mediator request handler that
depends on ISender or IMediator: a constructor parameter (a primary constructor's too), a field or a property, its
base classes' included, of either type, of anything that is one (the mediator itself, an interface deriving from them),
or of one wrapped (Lazy<ISender>, Func<IMediator>, an array). The failure names each, by layer:
Infrastructure: Titan.Shard.Infrastructure.Sync.SyncShardCommandHandler → constructor parameter 'sender' (ISender)
A request handler is a type implementing ICommandHandler<>, ICommandHandler<,>, IQueryHandler<,>,
IRequestHandler<,>, IValueTaskRequestHandler<,> or IStreamRequestHandler<,>, or a scatter-gather request handler
(IScatterGatherRequestHandler<,,,>, IStreamingScatterGatherRequestHandler<,,,>), which is sent like any request.
Why: a request sent from inside a handler hides a workflow in the pipeline. The inner request runs the behaviors again inside the outer one (logging, validation, a second unit of work and transaction, caching), its failure has to be mapped back by hand, and the use case is no longer readable in one place. Only the entry points send: endpoints, Quartz command jobs, bus consumers, UI handlers (a Discord command, an autocomplete). A handler that needs another use case's work does it through that work's abstractions (a repository, a domain service, a client), or the entry point sends both requests.
What the rule allows:
IPublisher: publishing a notification (a domain event) isn't sending a request.- Integration event handlers (
IIntegrationEventHandler<>), scatter-gather event handlers (IScatterGatherHandler<,,>) and request responders (IRequestResponder<,>): they are bus consumers, entry points of their own, so they may send. So may notification handlers (INotificationHandler<>,IDomainEventHandler<>): the rule is about request handlers only. - Pipeline behaviors: they are the pipeline, not a handler.
- A nested type: it is a type of its own. A handler isn't failed for what a type nested in it holds (and a handler nested in a static class is checked like any other).
Every layer assembly is checked, not only Application: a handler can live in Infrastructure (a scatter-gather request handler) or in any assembly the mediator scans. The rule has no allow-list; a service that must keep a sending handler for now skips the test with the reason, as above, until it is moved.
Configuration is read at registration
ConventionalConfigurationTests.Types_Should_NotTakeIConfigurationInAConstructor fails for a class with a constructor
(of any visibility) that takes IConfiguration, or anything implementing it (IConfigurationSection,
IConfigurationRoot, ConfigurationManager), in any layer assembly:
Infrastructure: Titan.Shard.Infrastructure.SwgohApiClient → constructor parameter 'configuration' (IConfiguration)
A service reads configuration where it registers (Program, the Add* registration extensions) and binds it to
options validated at start-up (AddOptions<T>().BindConfiguration(…).ValidateOnStart()), which its classes take as
IOptions<T>: a missing or invalid setting then fails the start, not the first request that reads it, and a test
hands a class its options without building a configuration. Only constructors are checked: a registration extension
method taking IConfiguration as a parameter is where configuration belongs. The generated Program class of a
top-level program is left out.
Sample project
samples/Resrcify.SharedKernel.WebApiExample/tests/Resrcify.SharedKernel.WebApiExample.ArchitectureTests in the
repository.
| 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
- NetArchTest.Rules (>= 1.3.2)
- Resrcify.SharedKernel.Abstractions (>= 4.7.0)
- Resrcify.SharedKernel.DomainDrivenDesign (>= 4.7.0)
- Resrcify.SharedKernel.Results (>= 4.7.0)
- Shouldly (>= 4.3.0)
- xunit (>= 2.9.3)
- Xunit.SkippableFact (>= 1.5.85)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 4.7.0 | 53 | 10/19/2026 |
| 4.6.0 | 61 | 10/19/2026 |
| 4.5.0 | 138 | 10/18/2026 |
| 4.4.1 | 77 | 10/17/2026 |
| 4.4.0 | 66 | 10/16/2026 |
| 4.3.0 | 74 | 10/16/2026 |
| 4.2.0 | 87 | 10/14/2026 |
| 4.1.0 | 78 | 10/14/2026 |
| 4.0.0 | 113 | 10/13/2026 |
| 3.0.0 | 151 | 10/3/2026 |
| 2.2.0 | 129 | 9/13/2026 |
| 2.1.1 | 1,336 | 6/8/2026 |
| 2.1.0 | 125 | 5/24/2026 |
| 2.0.0 | 131 | 5/22/2026 |
| 1.16.3 | 249 | 5/7/2026 |
| 1.16.2 | 121 | 5/7/2026 |
| 1.16.1 | 122 | 5/7/2026 |