Skip to content

Add ClusterArchTestsBase for reusable architecture test rules with ArchUnit.NET#46

Draft
Copilot wants to merge 10 commits into
mainfrom
copilot/add-architecture-tests-structure
Draft

Add ClusterArchTestsBase for reusable architecture test rules with ArchUnit.NET#46
Copilot wants to merge 10 commits into
mainfrom
copilot/add-architecture-tests-structure

Conversation

Copy link
Copy Markdown

Copilot AI commented Dec 13, 2025

Implements a reusable architecture test framework using ArchUnit.NET that enforces cluster-level architecture rules. Each module (A, B, C, etc.) can inherit from a shared base without duplicating test code.

ClusterArchTestsBase

New project containing:

  • ClusterConfiguration: Assembly naming convention mapper ({prefix}.Entities.Abstractions, {prefix}.ViewModels, etc.)
  • ClusterArchitectureRulesBase: 13 architecture rules covering:
    • Dependency layering (Entities → Library → ViewContracts → ViewModels/Views)
    • Access modifiers (DataManager/Handler internal, UseCase public, ViewModel classes internal, ViewContracts interfaces public)
    • Interface requirements (IView, IViewModel)

Marker Interfaces

Added IView and IViewModel in Bookmarkly.Entities.Abstractions for contract enforcement.

Usage

public sealed class ModuleAArchRules : ClusterArchitectureRulesBase
{
    public ModuleAArchRules() : base(new ClusterConfiguration("ModuleA")) { }

    protected override IEnumerable<Assembly> GetAssemblies()
    {
        yield return typeof(ModuleA.Entities.SomeType).Assembly;
        yield return typeof(ModuleA.ViewModels.SomeVM).Assembly;
        // ...
    }
}

[TestClass]
public sealed class ModuleAArchTests
{
    private static readonly ModuleAArchRules Rules = new();

    [TestMethod]
    public void EntitiesShouldNotDependOnOtherClusterProjects() =>
        Rules.EntitiesShouldNotDependOnOtherClusterProjects.Check(Rules.Architecture);
}

Other Changes

  • Added InternalsVisibleTo for ViewModels to expose internals to test projects
  • Fixed Directory.Build.props casing for Linux CI compatibility
  • Added EnableWindowsTargeting for cross-platform builds
Original prompt

If you were to design ArchitectureTests for ensuring that the workspace/project cluster follows the architecture by using ArchUnit.NET, how would you create architecture and the rules and the checks? A high level overview of it would be

  1. Entities can be referred by Library, ViewContracts, ViewModels and Views.
  2. Library can be referred by only ViewModels.
  3. ViewContracts acts as a bridge between Views and ViewModels and can be referred by both.
  4. ViewModels can be referred by none of the cluster projects. Only the host app can reference ViewModels.
  5. Views can be referred by only the host app.

Access modifiers.

  1. All classes that ends with DataManager/DataManagerBase/DM must be internal. Only the interfaces that ends like this can be public.
  2. All classes and interfaces that ends with Handler must be internal.
  3. All classes that ends with Usecase must be public.
  4. All classes that extends a UIElement defined in Views must extend the IView interface. and have an backing equivalent interface in ViewContracts counterpart.
  5. All classes that is defined in ViewModels must be internal. All classes in ViewModels that ends with ViewModel/ViewModelBase/VM must implement IViewModel interface and have an equivalent backing interface in ViewContracts. Eg: NotesListViewModel must implement INotesListViewModel.
  6. All interfaces defined in ViewContracts must be public. View interfaces must implement the IView interface. ViewModel interfaces must define the IViewModel interface by default.

But the thing is, this workspace is a miniature app which has only one module/cluster. I'm planning to adopt this to a Suite where there are multiple modules/clusters which has the same sets of projects per module. Say A, B, C which are integrated into the host app. Now the architecture rules at a foundational level apply to all the clusters. So I would want the architecture, rules and checks to not be duplicated. But at the same time have some mechanism where I can run the tests for each module separately as well. Probably reusing this code and passing the necessary info such as Assembly name or whatever? Or have this as base arch test project which can be used and a single method can accept the values it needs for those tests to work? So I can create ModuleAArchTests, ModuleBBArchTests which references ClusterArchTestsBase? But those tests projects will reside in the respective module clusters.


💬 We'd love your input! Share your thoughts on Copilot coding agent in our 2 minute survey.

Copilot AI and others added 8 commits November 22, 2025 21:54
* Initial plan

* Add AOT and trimming support: Update packages and project properties

Co-authored-by: Kumara-Krishnan <[email protected]>

* Add AOT compatibility to Bookmarkly.Views project

Co-authored-by: Kumara-Krishnan <[email protected]>

* Remove PublishTrimmed=false override from CI workflow

Co-authored-by: Kumara-Krishnan <[email protected]>

---------

Co-authored-by: copilot-swe-agent[bot] <[email protected]>
Co-authored-by: Kumara-Krishnan <[email protected]>
…621.0 (#33)

* Initial plan

* Update Windows SDK version from 10.0.22000.0 to 10.0.22621.0

Co-authored-by: Kumara-Krishnan <[email protected]>

---------

Co-authored-by: copilot-swe-agent[bot] <[email protected]>
Co-authored-by: Kumara-Krishnan <[email protected]>
* Initial plan

* Add .binlog artifacts to CI workflows for build debugging



---------

Co-authored-by: Copilot <[email protected]>
…lease on self-hosted runner (#41)

* Add binary log artifacts to CI build workflows (#35)

* Initial plan

* Add .binlog artifacts to CI workflows for build debugging

Co-authored-by: Kumara-Krishnan <[email protected]>

---------

Co-authored-by: copilot-swe-agent[bot] <[email protected]>
Co-authored-by: Kumara-Krishnan <[email protected]>

* [WIP] Run msix build and release on self-hosted runner (#38)

* Initial plan

* Update MSIX workflow to use self-hosted runner with VS 2026

Co-authored-by: Kumara-Krishnan <[email protected]>

* Address code review: Support both VS 2022 and VS 2026

Co-authored-by: Kumara-Krishnan <[email protected]>

---------

Co-authored-by: copilot-swe-agent[bot] <[email protected]>
Co-authored-by: Kumara-Krishnan <[email protected]>

---------

Co-authored-by: Copilot <[email protected]>
Copilot AI self-assigned this Dec 13, 2025
- Create ClusterArchTestsBase project with reusable architecture rules
- Add ClusterConfiguration class for module/cluster assembly naming
- Add ClusterArchitectureRulesBase with 13 architecture rules covering:
  - Dependency rules (Entities, Library, ViewContracts, ViewModels, Views)
  - Access modifier rules (DataManager, Handler, UseCase, ViewModel)
  - Interface implementation rules (IView, IViewModel)
- Update ArchitectureTests to use the base project for Bookmarkly
- Add placeholder classes to enable assembly reference for testing
- Add InternalsVisibleTo for ViewModels to expose internals to tests
- Fix Directory.Build.props and Directory.Packages.props casing
- Enable cross-platform Windows targeting for CI builds

Co-authored-by: Kumara-Krishnan <[email protected]>
Copilot AI changed the title [WIP] Add architecture tests using ArchUnit.NET Add ClusterArchTestsBase for reusable architecture test rules with ArchUnit.NET Dec 13, 2025
@Kumara-Krishnan
Copy link
Copy Markdown
Collaborator

@copilot Does it cover transitive dependencies as well?

Copy link
Copy Markdown
Author

Copilot AI commented Dec 14, 2025

@copilot Does it cover transitive dependencies as well?

No, the current implementation using NotDependOnAny() only checks for direct dependencies between types. It analyzes direct type references (field types, method parameters, base classes, interface implementations, etc.) but does not traverse transitive dependency chains.

For example, if Views → TypeA → ViewModels, the rule ViewsShouldNotDependOnViewModels would not catch this because Views doesn't directly reference ViewModels types.

However, in practice, transitive violations are often caught indirectly because:

  1. The architecture rules validate each layer independently - if TypeA violates a rule, that violation would be caught by the rule for TypeA's layer
  2. Project-level references (.csproj references) would typically prevent transitive issues at compile time

If you need explicit transitive dependency checking, ArchUnit.NET doesn't provide this out-of-the-box. We could potentially add custom rules that traverse the dependency graph, but this would require additional implementation. Would you like me to add transitive dependency validation?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants