Skip to content

Commit

Permalink
Issue: TNG#475 - Implement ArchRule that checks if test class reside …
Browse files Browse the repository at this point in the history
…in the same package as implementation class

Signed-off-by: Marcin Słowiak <marcin.slowiak.007@gmail.com>
  • Loading branch information
mslowiak committed Jul 11, 2022
1 parent cd783af commit 3355e0d
Show file tree
Hide file tree
Showing 8 changed files with 142 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,11 @@
*/
package com.tngtech.archunit.library;

import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.stream.Collectors;

import com.tngtech.archunit.PublicAPI;
import com.tngtech.archunit.core.domain.AccessTarget.FieldAccessTarget;
import com.tngtech.archunit.core.domain.JavaAccess.Functions.Get;
Expand All @@ -24,6 +29,7 @@
import com.tngtech.archunit.core.importer.ClassFileImporter;
import com.tngtech.archunit.lang.ArchCondition;
import com.tngtech.archunit.lang.ArchRule;
import com.tngtech.archunit.lang.ConditionEvents;

import static com.tngtech.archunit.PublicAPI.Usage.ACCESS;
import static com.tngtech.archunit.base.DescribedPredicate.not;
Expand All @@ -37,13 +43,15 @@
import static com.tngtech.archunit.core.domain.properties.HasOwner.Predicates.With.owner;
import static com.tngtech.archunit.core.domain.properties.HasParameterTypes.Predicates.rawParameterTypes;
import static com.tngtech.archunit.core.domain.properties.HasType.Functions.GET_RAW_TYPE;
import static com.tngtech.archunit.lang.SimpleConditionEvent.violated;
import static com.tngtech.archunit.lang.conditions.ArchConditions.accessField;
import static com.tngtech.archunit.lang.conditions.ArchConditions.beAnnotatedWith;
import static com.tngtech.archunit.lang.conditions.ArchConditions.callCodeUnitWhere;
import static com.tngtech.archunit.lang.conditions.ArchConditions.callMethodWhere;
import static com.tngtech.archunit.lang.conditions.ArchConditions.dependOnClassesThat;
import static com.tngtech.archunit.lang.conditions.ArchConditions.setFieldWhere;
import static com.tngtech.archunit.lang.conditions.ArchPredicates.is;
import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.classes;
import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.noClasses;
import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.noFields;

Expand Down Expand Up @@ -407,4 +415,54 @@ private static ArchCondition<JavaField> beAnnotatedWithAnInjectionAnnotation() {
.as("no classes should use field injection")
.because("field injection is considered harmful; use constructor injection or setter injection instead; "
+ "see https://stackoverflow.com/q/39890849 for detailed explanations");

/**
* A rule that checks that every test class has the same package as implementation class.
*/
@PublicAPI(usage = ACCESS)
public static ArchRule testClassesShouldResideInTheSamePackageAsImplementation(String testClassPattern) {
return classes().should(new ArchCondition<JavaClass>("have the same package as test classes") {
Map<String, String> simpleTestNameToPackage = new HashMap<>();

@Override
public void init(Collection<JavaClass> allClasses) {
simpleTestNameToPackage = allClasses.stream()
.filter(clazz -> clazz.getName().endsWith(testClassPattern))
.collect(Collectors.toMap(JavaClass::getSimpleName, JavaClass::getPackageName));
}

@Override
public void check(JavaClass implementationClass, ConditionEvents events) {
final String implementationClassName = implementationClass.getSimpleName();
final String implementationClassPackageName = implementationClass.getPackageName();
final String possibleTestClassName = implementationClassName + testClassPattern;
final String possibleTestClassPackageName = simpleTestNameToPackage.get(possibleTestClassName);

final boolean isTestClassInWrongPackage = possibleTestClassPackageName != null
&& !possibleTestClassPackageName.equals(implementationClassPackageName);

if (isTestClassInWrongPackage) {
events.add(
violated(
implementationClass,
String.format(
"Test class %s.%s is in the wrong package. Should be inside %s package",
possibleTestClassPackageName, possibleTestClassName, implementationClassPackageName
)
)
);
}
}
});
}

/**
* A rule that checks that every test class has the same package as implementation class.
*/
@PublicAPI(usage = ACCESS)
public static ArchRule testClassesShouldResideInTheSamePackageAsImplementation() {
final String defaultTestClassSuffix = "Test";
return testClassesShouldResideInTheSamePackageAsImplementation(defaultTestClassSuffix);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package com.tngtech.archunit.library;

import com.tngtech.archunit.core.domain.JavaClasses;
import com.tngtech.archunit.core.importer.ClassFileImporter;
import com.tngtech.archunit.library.testclasses.packages.correct.SecondClass;
import com.tngtech.archunit.library.testclasses.packages.incorrect.FirstClass;
import org.junit.Test;

import static com.tngtech.archunit.library.GeneralCodingRules.testClassesShouldResideInTheSamePackageAsImplementation;
import static org.assertj.core.api.Assertions.assertThatNoException;
import static org.assertj.core.api.Assertions.assertThatThrownBy;

public class TestClassesPackagesAlignedWithImplementationTest {

private final JavaClasses correctClasses = new ClassFileImporter().importPackagesOf(SecondClass.class);
private final JavaClasses incorrectClasses = new ClassFileImporter().importPackagesOf(FirstClass.class);

@Test
public void should_fail_when_test_class_reside_in_different_package_as_implementation() {
// given
final String correctPackage = "com.tngtech.archunit.library.testclasses.packages.incorrect";
final String incorrectTestClass = "com.tngtech.archunit.library.testclasses.packages.incorrect.dir.FirstClassTest";
final String expectedErrorMessage = "Test class " + incorrectTestClass + " is in the wrong package. Should be inside " + correctPackage + " package";

// when & then
assertThatThrownBy(() -> testClassesShouldResideInTheSamePackageAsImplementation().check(incorrectClasses))
.isInstanceOf(AssertionError.class)
.hasMessageContaining(expectedErrorMessage);
}

@Test
public void should_fail_when_test_class_reside_in_different_package_as_implementation_when_have_custom_suffix() {
// given
final String correctPackage = "com.tngtech.archunit.library.testclasses.packages.incorrect";
final String incorrectTestClass = "com.tngtech.archunit.library.testclasses.packages.incorrect.dir.FirstClassTestingScenario";
final String expectedErrorMessage = "Test class " + incorrectTestClass + " is in the wrong package. Should be inside " + correctPackage + " package";
final String testClassesSuffix = "TestingScenario";

// when & then
assertThatThrownBy(() -> testClassesShouldResideInTheSamePackageAsImplementation(testClassesSuffix).check(incorrectClasses))
.isInstanceOf(AssertionError.class)
.hasMessageContaining(expectedErrorMessage);
}

@Test
public void should_not_fail_when_test_class_and_implementation_class_reside_in_the_same_package() {
// when & then
assertThatNoException().isThrownBy(() -> testClassesShouldResideInTheSamePackageAsImplementation().check(correctClasses));
}

@Test
public void should_not_fail_when_test_class_and_implementation_class_reside_in_the_same_package_with_custom_suffix() {
// given
final String testClassesSuffix = "TestingScenario";

// when & then
assertThatNoException().isThrownBy(() -> testClassesShouldResideInTheSamePackageAsImplementation(testClassesSuffix).check(correctClasses));
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package com.tngtech.archunit.library.testclasses.packages.correct;

public class SecondClass {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package com.tngtech.archunit.library.testclasses.packages.correct;

class SecondClassTest {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package com.tngtech.archunit.library.testclasses.packages.correct;

class SecondClassTestingScenario {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package com.tngtech.archunit.library.testclasses.packages.incorrect;

public class FirstClass {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package com.tngtech.archunit.library.testclasses.packages.incorrect.dir;

class FirstClassTest {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package com.tngtech.archunit.library.testclasses.packages.incorrect.dir;

class FirstClassTestingScenario {
}

0 comments on commit 3355e0d

Please sign in to comment.