Skip to content

Commit

Permalink
add OnionArchitecture.ensureAllClassesAreContainedInArchitecture()
Browse files Browse the repository at this point in the history
This will give users the possibility to ensure that they have not missed any classes of the application when defining an `onionArchitecture()`.

Signed-off-by: Rob Oxspring <roxspring@imapmail.org>
  • Loading branch information
roxspring authored and codecholeric committed Jun 30, 2022
1 parent ca2ba4d commit 3352e33
Show file tree
Hide file tree
Showing 2 changed files with 114 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -766,6 +766,7 @@ public static final class OnionArchitecture implements ArchRule {
private Map<String, DescribedPredicate<? super JavaClass>> adapterPredicates = new LinkedHashMap<>();
private boolean optionalLayers = false;
private List<IgnoredDependency> ignoredDependencies = new ArrayList<>();
private AllClassesAreContainedInArchitectureCheck allClassesAreContainedInArchitectureCheck = new AllClassesAreContainedInArchitectureCheck.Disabled();

private OnionArchitecture() {
overriddenDescription = Optional.empty();
Expand Down Expand Up @@ -856,6 +857,46 @@ public OnionArchitecture ignoreDependency(DescribedPredicate<? super JavaClass>
return this;
}

/**
* Ensure that all classes under test are contained within a defined onion architecture component.
*
* @see #ensureAllClassesAreContainedInArchitectureIgnoring(String...)
* @see #ensureAllClassesAreContainedInArchitectureIgnoring(DescribedPredicate)
*/
@PublicAPI(usage = ACCESS)
public OnionArchitecture ensureAllClassesAreContainedInArchitecture() {
return ensureAllClassesAreContainedInArchitectureIgnoring(alwaysFalse());
}

/**
* Like {@link #ensureAllClassesAreContainedInArchitecture()} but will ignore classes in packages matching
* the specified {@link PackageMatcher packageIdentifiers}.
*
* @param packageIdentifiers {@link PackageMatcher packageIdentifiers} specifying which classes may live outside the architecture
*
* @see #ensureAllClassesAreContainedInArchitecture()
* @see #ensureAllClassesAreContainedInArchitectureIgnoring(DescribedPredicate)
*/
@PublicAPI(usage = ACCESS)
public OnionArchitecture ensureAllClassesAreContainedInArchitectureIgnoring(String... packageIdentifiers) {
return ensureAllClassesAreContainedInArchitectureIgnoring(resideInAnyPackage(packageIdentifiers));
}

/**
* Like {@link #ensureAllClassesAreContainedInArchitecture()} but will ignore classes in packages matching
* the specified {@link DescribedPredicate predicate}.
*
* @param predicate {@link DescribedPredicate predicate} specifying which classes may live outside the architecture
*
* @see #ensureAllClassesAreContainedInArchitecture()
* @see #ensureAllClassesAreContainedInArchitectureIgnoring(String...)
*/
@PublicAPI(usage = ACCESS)
public OnionArchitecture ensureAllClassesAreContainedInArchitectureIgnoring(DescribedPredicate<? super JavaClass> predicate) {
allClassesAreContainedInArchitectureCheck = new AllClassesAreContainedInArchitectureCheck.Enabled(predicate);
return this;
}

private DescribedPredicate<JavaClass> byPackagePredicate(String[] packageIdentifiers) {
return resideInAnyPackage(packageIdentifiers).as(joinSingleQuoted(packageIdentifiers));
}
Expand All @@ -877,9 +918,13 @@ private LayeredArchitecture layeredArchitectureDelegate() {
.layer(adapterLayer).definedBy(adapter.getValue())
.whereLayer(adapterLayer).mayNotBeAccessedByAnyLayer();
}

for (IgnoredDependency ignoredDependency : this.ignoredDependencies) {
layeredArchitectureDelegate = ignoredDependency.ignoreFor(layeredArchitectureDelegate);
}

layeredArchitectureDelegate = allClassesAreContainedInArchitectureCheck.configure(layeredArchitectureDelegate);

return layeredArchitectureDelegate.as(getDescription());
}

Expand Down Expand Up @@ -955,5 +1000,29 @@ LayeredArchitecture ignoreFor(LayeredArchitecture layeredArchitecture) {
return layeredArchitecture.ignoreDependency(origin, target);
}
}

private abstract static class AllClassesAreContainedInArchitectureCheck {
abstract LayeredArchitecture configure(LayeredArchitecture layeredArchitecture);

static class Enabled extends AllClassesAreContainedInArchitectureCheck {
private final DescribedPredicate<? super JavaClass> ignorePredicate;

private Enabled(DescribedPredicate<? super JavaClass> ignorePredicate) {
this.ignorePredicate = ignorePredicate;
}

@Override
LayeredArchitecture configure(LayeredArchitecture layeredArchitecture) {
return layeredArchitecture.ensureAllClassesAreContainedInArchitectureIgnoring(ignorePredicate);
}
}

static class Disabled extends AllClassesAreContainedInArchitectureCheck {
@Override
LayeredArchitecture configure(LayeredArchitecture layeredArchitecture) {
return layeredArchitecture;
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@
import com.tngtech.archunit.lang.ArchRule;
import com.tngtech.archunit.lang.EvaluationResult;
import com.tngtech.archunit.library.Architectures.OnionArchitecture;
import com.tngtech.archunit.library.testclasses.coveringallclasses.first.First;
import com.tngtech.archunit.library.testclasses.coveringallclasses.second.Second;
import com.tngtech.archunit.library.testclasses.coveringallclasses.third.Third;
import com.tngtech.archunit.library.testclasses.onionarchitecture.adapter.cli.CliAdapterLayerClass;
import com.tngtech.archunit.library.testclasses.onionarchitecture.adapter.persistence.PersistenceAdapterLayerClass;
import com.tngtech.archunit.library.testclasses.onionarchitecture.adapter.rest.RestAdapterLayerClass;
Expand All @@ -24,6 +27,7 @@
import static com.tngtech.archunit.base.DescribedPredicate.alwaysTrue;
import static com.tngtech.archunit.core.domain.JavaClass.Predicates.equivalentTo;
import static com.tngtech.archunit.core.domain.JavaClass.Predicates.resideInAnyPackage;
import static com.tngtech.archunit.core.domain.JavaClass.Predicates.simpleName;
import static com.tngtech.archunit.core.domain.JavaClass.Predicates.simpleNameContaining;
import static com.tngtech.archunit.core.domain.JavaClass.Predicates.simpleNameStartingWith;
import static com.tngtech.archunit.library.Architectures.onionArchitecture;
Expand All @@ -32,6 +36,7 @@
import static com.tngtech.archunit.library.LayeredArchitectureTest.expectedAccessViolationPattern;
import static com.tngtech.archunit.library.LayeredArchitectureTest.expectedEmptyLayerPattern;
import static com.tngtech.archunit.library.LayeredArchitectureTest.expectedFieldTypePattern;
import static com.tngtech.archunit.testutil.Assertions.assertThatRule;
import static com.tngtech.java.junit.dataprovider.DataProviders.testForEach;
import static java.beans.Introspector.decapitalize;
import static java.lang.System.lineSeparator;
Expand Down Expand Up @@ -195,6 +200,46 @@ public void onion_architecture_rejects_empty_layers_if_layers_are_explicitly_not
assertFailureOnionArchitectureWithEmptyLayers(result);
}

@Test
public void onion_architecture_ensure_all_classes_are_contained_in_architecture() {
JavaClasses classes = new ClassFileImporter().importClasses(First.class, Second.class);

OnionArchitecture architectureNotCoveringAllClasses = onionArchitecture().withOptionalLayers(true)
.domainModels("..first..")
.ensureAllClassesAreContainedInArchitecture();

assertThatRule(architectureNotCoveringAllClasses).checking(classes)
.hasOnlyOneViolation("Class <" + Second.class.getName() + "> is not contained in architecture");

OnionArchitecture architectureCoveringAllClasses = architectureNotCoveringAllClasses
.domainServices("..second..");
assertThatRule(architectureCoveringAllClasses).checking(classes).hasNoViolation();
}

@Test
public void onion_architecture_ensure_all_classes_are_contained_in_architecture_ignoring_packages() {
JavaClasses classes = new ClassFileImporter().importClasses(First.class, Second.class, Third.class);

OnionArchitecture architecture = onionArchitecture().withOptionalLayers(true)
.domainModels("..first..")
.ensureAllClassesAreContainedInArchitectureIgnoring("..second..");

assertThatRule(architecture).checking(classes)
.hasOnlyOneViolation("Class <" + Third.class.getName() + "> is not contained in architecture");
}

@Test
public void onion_architecture_ensure_all_classes_are_contained_in_architecture_ignoring_predicate() {
JavaClasses classes = new ClassFileImporter().importClasses(First.class, Second.class, Third.class);

OnionArchitecture architecture = onionArchitecture().withOptionalLayers(true)
.domainModels("..first..")
.ensureAllClassesAreContainedInArchitectureIgnoring(simpleName("Second"));

assertThatRule(architecture).checking(classes)
.hasOnlyOneViolation("Class <" + Third.class.getName() + "> is not contained in architecture");
}

private static OnionArchitecture getTestOnionArchitecture() {
return onionArchitecture()
.domainModels(absolute("onionarchitecture.domain.model"))
Expand Down

0 comments on commit 3352e33

Please sign in to comment.