<

Trying ArchUnit with Typescript

I recently learned about something called ArchUnit. It seems that it can test dependencies. I want to try it out right away, so I'll leave this as a memo.

ArchUnit

https://www.archunit.org/

ArchUnit is a free, simple and extensible library for checking the architecture of your Java code using any plain Java unit test framework. That is, ArchUnit can check dependencies between packages and classes, layers and slices, check for cyclic dependencies and more. It does so by analyzing given Java bytecode, importing all classes into a Java code structure.

It's a library that can test Java architecture, and it seems to be able to test dependencies between packages, classes, layers, and slices(?). So, I want to test this diagram that I've seen more than my parents' faces.

Clean Coder Blog > The Clean Architecture
Clean Coder Blog > The Clean Architecture

I want to do ArchUnit with Typescript

ArchUnit is made in Java. I want to do ArchUnit with Typescript. So, I found a good library.

https://github.com/MaibornWolff/ts-arch

I don't have any particular preference, I think anything that can test the architecture is fine. In extreme cases, I think you could make your own by parsing the source code into AST and extracting dependencies.

I tried it

The source code I tried is placed below. Please refer to it.

https://github.com/silverbirder/try-archunit

The overall source code tree is as follows.

src
└ 1_enterprise_business_rules
  └ entities
    └ Entity.ts
└ 2_application_business_rules
  └ use_cases
    └ UseCase.ts
└ 3_interface_adapters
  └ controllers
    └ Controller.ts
  └ gateways
    └ Gateway.ts
  └ presenters
    └ Presenter.ts
└ 4_frameworks_and_drivers
  └ web
    └ Web.ts
└ clean_architecture.puml
└ clean_architecture.test.ts

Each product code is just importing files from the lower hierarchy.

// src/4_frameworks_and_drivers/web/Web.ts
import "../../3_interface_adapters/gateways/Gateway";
import "../../3_interface_adapters/controllers/Controller";
import "../../3_interface_adapters/presenters/Presenter";
// src/3_interface_adapters/controllers/Controller.ts
import "../../2_application_business_rules/use_cases/UseCase";
// src/2_application_business_rules/use_cases/UseCase.ts
import "../../1_enterprise_business_rules/entities/Entity";
// src/1_enterprise_business_rules/entities/Entity.ts

The dependency is represented in the component diagram in the following file.

# clean_architecture.puml
@startuml
  component [4_frameworks_and_drivers] #Blue
  component [3_interface_adapters] #Green
  component [2_application_business_rules] #Red
  component [1_enterprise_business_rules] #Yellow

  4_frameworks_and_drivers --> 3_interface_adapters
  3_interface_adapters --> 2_application_business_rules
  2_application_business_rules --> 1_enterprise_business_rules
@enduml

When visualizing the UML, it looks like the following diagram.

clean_architecture.puml
clean_architecture.puml

The test code is as follows.

// clean_architecture.test.ts
describe("architecture", () => {
  it("Check dependency", async () => {
    const architectureUml = path.resolve(__dirname, "clean_architecture.puml");
    const violations = await slicesOfProject()
      .definedBy("src/(**)/")
      .should()
      .adhereToDiagramInFile(architectureUml)
      .check();
    await expect(violations).toEqual([]);
  });
});

This test case will PASS.

src/clean_architecture.test.ts > architecture > Check dependency #Succeed
src/clean_architecture.test.ts > architecture > Check dependency #Succeed

Now, let's write some violation code.

// src/3_interface_adapters/controllers/Controller.ts
import "../../2_application_business_rules/use_cases/UseCase";
import "../../4_frameworks_and_drivers/web/Web";

The 3rd layer is using the higher 4th layer. If you run the test in this state,

src/clean_architecture.test.ts > architecture > Check dependency #Failed
src/clean_architecture.test.ts > architecture > Check dependency #Failed

It has beautifully Failed. In other words, you can automatically detect errors in dependencies.

Finally

The larger the project, the more complex the dependencies tend to be. Even if you have properly designed the dependencies of (what would be in Java) packages and classes, someone might break them. It's a shame to have your carefully crafted design broken, so let's protect it with test code!

If it was helpful, support me with a ☕!

Share

Related tags