Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Junit4-support for @ArchTest instance fields or non-static methods in abstract base classes #105

Merged
merged 4 commits into from
Aug 30, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -34,22 +34,24 @@
abstract class ArchRuleDeclaration<T extends AnnotatedElement> {
private final Class<?> testClass;
final T declaration;
final Class<?> owner;
private final boolean forceIgnore;

ArchRuleDeclaration(Class<?> testClass, T declaration, boolean forceIgnore) {
ArchRuleDeclaration(Class<?> testClass, T declaration, Class<?> owner, boolean forceIgnore) {
this.testClass = testClass;
this.declaration = declaration;
this.owner = owner;
this.forceIgnore = forceIgnore;
}

abstract void handleWith(Handler handler);

private static ArchRuleDeclaration<Method> from(Class<?> testClass, Method method, boolean forceIgnore) {
return new AsMethod(testClass, method, forceIgnore);
private static ArchRuleDeclaration<Method> from(Class<?> testClass, Method method, Class<?> methodOwner, boolean forceIgnore) {
return new AsMethod(testClass, method, methodOwner, forceIgnore);
}

private static ArchRuleDeclaration<Field> from(Class<?> testClass, Field field, boolean forceIgnore) {
return new AsField(testClass, field, forceIgnore);
private static ArchRuleDeclaration<Field> from(Class<?> testClass, Field field, Class<?> fieldOwner, boolean forceIgnore) {
return new AsField(testClass, field, fieldOwner, forceIgnore);
}

static <T extends AnnotatedElement & Member> boolean elementShouldBeIgnored(T member) {
Expand All @@ -69,54 +71,54 @@ static Set<ArchRuleDeclaration<?>> toDeclarations(
ArchRules rules, Class<?> testClass, Class<? extends Annotation> archTestAnnotationType, boolean forceIgnore) {

ImmutableSet.Builder<ArchRuleDeclaration<?>> result = ImmutableSet.builder();
for (Field field : getAllFields(rules.getDefinitionLocation(), withAnnotation(archTestAnnotationType))) {
result.addAll(archRuleDeclarationsFrom(testClass, field, archTestAnnotationType, forceIgnore));
Class<?> definitionLocation = rules.getDefinitionLocation();
for (Field field : getAllFields(definitionLocation, withAnnotation(archTestAnnotationType))) {
result.addAll(archRuleDeclarationsFrom(testClass, field, definitionLocation, archTestAnnotationType, forceIgnore));
}
for (Method method : getAllMethods(rules.getDefinitionLocation(), withAnnotation(archTestAnnotationType))) {
result.add(ArchRuleDeclaration.from(testClass, method, forceIgnore));
for (Method method : getAllMethods(definitionLocation, withAnnotation(archTestAnnotationType))) {
result.add(ArchRuleDeclaration.from(testClass, method, definitionLocation, forceIgnore));
}
return result.build();
}

private static Set<ArchRuleDeclaration<?>> archRuleDeclarationsFrom(Class<?> testClass, Field field,
private static Set<ArchRuleDeclaration<?>> archRuleDeclarationsFrom(Class<?> testClass, Field field, Class<?> fieldOwner,
Class<? extends Annotation> archTestAnnotationType, boolean forceIgnore) {

return ArchRules.class.isAssignableFrom(field.getType()) ?
toDeclarations(getArchRulesIn(field), testClass, archTestAnnotationType, forceIgnore || elementShouldBeIgnored(field)) :
Collections.<ArchRuleDeclaration<?>>singleton(ArchRuleDeclaration.from(testClass, field, forceIgnore));
toDeclarations(getArchRulesIn(field, fieldOwner), testClass, archTestAnnotationType, forceIgnore || elementShouldBeIgnored(field)) :
Collections.<ArchRuleDeclaration<?>>singleton(ArchRuleDeclaration.from(testClass, field, fieldOwner, forceIgnore));
}

private static ArchRules getArchRulesIn(Field field) {
ArchRules value = getValue(field);
return checkNotNull(value, "Field %s.%s is not initialized",
field.getDeclaringClass().getName(), field.getName());
private static ArchRules getArchRulesIn(Field field, Class<?> fieldOwner) {
ArchRules value = getValue(field, fieldOwner);
return checkNotNull(value, "Field %s.%s is not initialized", fieldOwner.getName(), field.getName());
}

private static class AsMethod extends ArchRuleDeclaration<Method> {
AsMethod(Class<?> testClass, Method method, boolean forceIgnore) {
super(testClass, method, forceIgnore);
AsMethod(Class<?> testClass, Method method, Class<?> methodOwner, boolean forceIgnore) {
super(testClass, method, methodOwner, forceIgnore);
}

@Override
void handleWith(Handler handler) {
handler.handleMethodDeclaration(declaration, shouldBeIgnored());
handler.handleMethodDeclaration(declaration, owner, shouldBeIgnored());
}
}

private static class AsField extends ArchRuleDeclaration<Field> {
AsField(Class<?> testClass, Field field, boolean forceIgnore) {
super(testClass, field, forceIgnore);
AsField(Class<?> testClass, Field field, Class<?> fieldOwner, boolean forceIgnore) {
super(testClass, field, fieldOwner, forceIgnore);
}

@Override
void handleWith(Handler handler) {
handler.handleFieldDeclaration(declaration, shouldBeIgnored());
handler.handleFieldDeclaration(declaration, owner, shouldBeIgnored());
}
}

interface Handler {
void handleFieldDeclaration(Field field, boolean ignore);
void handleFieldDeclaration(Field field, Class<?> fieldOwner, boolean ignore);

void handleMethodDeclaration(Method method, boolean ignore);
void handleMethodDeclaration(Method method, Class<?> methodOwner, boolean ignore);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ class ArchRuleExecution extends ArchTestExecution {

@Override
Result evaluateOn(JavaClasses classes) {
ArchRule rule = getValue(ruleField);
ArchRule rule = getValue(ruleField, testClass);
try {
rule.check(classes);
} catch (Exception | AssertionError e) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,8 @@ boolean ignore() {
return ignore;
}

static <T> T getValue(Field field) {
return getValueOrThrowException(field, WRAP_CAUSE);
static <T> T getValue(Field field, Class<?> fieldOwner) {
return getValueOrThrowException(field, fieldOwner, WRAP_CAUSE);
}

abstract static class Result {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ private void executeTestMethod(JavaClasses classes) {
"Methods annotated with @%s must have exactly one parameter of type %s",
ArchTest.class.getSimpleName(), JavaClasses.class.getSimpleName());

invokeMethod(testMethod, classes);
invokeMethod(testMethod, testClass, classes);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ private Set<ArchTestExecution> asTestExecutions(ArchRules archRules, boolean for
}

private ArchRules getArchRules(Field field) {
return getValue(field);
return getValue(field, field.getDeclaringClass());
}

private Collection<ArchTestExecution> findArchRuleMethods() {
Expand Down Expand Up @@ -172,13 +172,13 @@ private static class ExecutionTransformer implements ArchRuleDeclaration.Handler
private final ImmutableSet.Builder<ArchTestExecution> executions = ImmutableSet.builder();

@Override
public void handleFieldDeclaration(Field field, boolean ignore) {
executions.add(new ArchRuleExecution(field.getDeclaringClass(), field, ignore));
public void handleFieldDeclaration(Field field, Class<?> fieldOwner, boolean ignore) {
executions.add(new ArchRuleExecution(fieldOwner, field, ignore));
}

@Override
public void handleMethodDeclaration(Method method, boolean ignore) {
executions.add(new ArchTestMethodExecution(method.getDeclaringClass(), method, ignore));
public void handleMethodDeclaration(Method method, Class<?> methodOwner, boolean ignore) {
executions.add(new ArchTestMethodExecution(methodOwner, method, ignore));
}

Set<ArchTestExecution> getExecutions() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,11 @@
import static com.tngtech.archunit.junit.ArchUnitRunnerRunsMethodsTest.ArchTestWithTestMethod.testSomething;
import static com.tngtech.archunit.junit.ArchUnitRunnerRunsMethodsTest.IgnoredArchTest.toBeIgnoredOne;
import static com.tngtech.archunit.junit.ArchUnitRunnerRunsMethodsTest.IgnoredArchTest.toBeIgnoredTwo;
import static com.tngtech.archunit.junit.ArchUnitRunnerTestUtils.BE_SATISFIED;
import static com.tngtech.archunit.junit.ArchUnitRunnerTestUtils.getRule;
import static com.tngtech.archunit.junit.ArchUnitRunnerTestUtils.newRunnerFor;
import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.all;
import static com.tngtech.archunit.lang.syntax.ClassesIdentityTransformer.classes;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.never;
Expand Down Expand Up @@ -91,25 +94,11 @@ public void fails_methods_with_no_parameters() {
runAndAssertWrongParametersForChild(noParams, newRunner(ArchTestWithIllegalTestMethods.class));
}

private ArchUnitRunner newRunner(Class<ArchTestWithIllegalTestMethods> testClass) {
return newRunnerFor(testClass, cache);
}

@Test
public void fails_methods_with_too_many_parameters() {
runAndAssertWrongParametersForChild(tooManyParams, newRunner(ArchTestWithIllegalTestMethods.class));
}

private void runAndAssertWrongParametersForChild(String name, ArchUnitRunner runner) {
runner.runChild(getRule(name, runner), runNotifier);
verify(runNotifier).fireTestFailure(failureCaptor.capture());
Failure failure = failureCaptor.getValue();
assertThat(failure.getDescription().toString()).as("Failure description").contains(name);
assertThat(failure.getException().getMessage()).as("Failure Cause")
.contains("@" + ArchTest.class.getSimpleName())
.contains("exactly one parameter of type " + JavaClasses.class.getSimpleName());
}

@Test
public void ignores_all_methods_in_classes_annotated_with_ArchIgnore() throws InitializationError {
ArchUnitRunner runner = new ArchUnitRunner(IgnoredArchTest.class);
Expand All @@ -131,6 +120,33 @@ public void ignores_methods_annotated_with_ArchIgnore() throws InitializationErr
assertThat(descriptionCaptor.getValue().toString()).contains(toBeIgnored);
}

@Test
public void should_allow_instance_method_in_abstract_base_class() {
ArchUnitRunner runner = newRunnerFor(ArchTestWithAbstractBaseClass.class, cache);

runner.runChild(ArchUnitRunnerTestUtils.getRule(AbstractBaseClass.INSTANCE_METHOD_NAME, runner), runNotifier);

verifyTestFinishedSuccessfully(AbstractBaseClass.INSTANCE_METHOD_NAME);
}

private ArchUnitRunner newRunner(Class<ArchTestWithIllegalTestMethods> testClass) {
return newRunnerFor(testClass, cache);
}

private void runAndAssertWrongParametersForChild(String name, ArchUnitRunner runner) {
runner.runChild(getRule(name, runner), runNotifier);
verify(runNotifier).fireTestFailure(failureCaptor.capture());
Failure failure = failureCaptor.getValue();
assertThat(failure.getDescription().toString()).as("Failure description").contains(name);
assertThat(failure.getException().getMessage()).as("Failure Cause")
.contains("@" + ArchTest.class.getSimpleName())
.contains("exactly one parameter of type " + JavaClasses.class.getSimpleName());
}

private void verifyTestFinishedSuccessfully(String expectedDescriptionMethodName) {
ArchUnitRunnerRunsRuleFieldsTest.verifyTestFinishedSuccessfully(runNotifier, descriptionCaptor, expectedDescriptionMethodName);
}

@AnalyzeClasses(packages = "some.pkg")
public static class ArchTestWithTestMethod {
static final String testSomething = "testSomething";
Expand Down Expand Up @@ -187,4 +203,17 @@ public static class ArchTestWithIgnoredMethod {
public static void toBeIgnored(JavaClasses classes) {
}
}

@AnalyzeClasses(packages = "some.pkg")
public static class ArchTestWithAbstractBaseClass extends AbstractBaseClass {
}

abstract static class AbstractBaseClass {
static final String INSTANCE_METHOD_NAME = "abstractBaseClassInstanceMethod";

@ArchTest
void abstractBaseClassInstanceMethod(JavaClasses classes) {
all(classes()).should(BE_SATISFIED).check(classes);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -91,9 +91,7 @@ public void should_accept_satisfied_rule() {

runner.runChild(satisfiedRule, runNotifier);

verify(runNotifier, never()).fireTestFailure(any(Failure.class));
verify(runNotifier).fireTestFinished(descriptionCaptor.capture());
assertThat(descriptionCaptor.getValue().toString()).contains(SATISFIED_FIELD_NAME);
verifyTestFinishedSuccessfully(SATISFIED_FIELD_NAME);
}

@Test
Expand All @@ -102,11 +100,16 @@ public void should_allow_instance_fields_of_all_visibility() {

runner.runChild(ArchUnitRunnerTestUtils.getRule(PRIVATE_RULE_FIELD_NAME, runner), runNotifier);

verify(runNotifier, never()).fireTestFailure(any(Failure.class));
verify(runNotifier).fireTestFinished(descriptionCaptor.capture());
verifyTestFinishedSuccessfully(PRIVATE_RULE_FIELD_NAME);
}

assertThat(descriptionCaptor.getAllValues()).extractingResultOf("getMethodName")
.contains(PRIVATE_RULE_FIELD_NAME);
@Test
public void should_allow_instance_field_in_abstract_base_class() {
ArchUnitRunner runner = newRunnerFor(ArchTestWithAbstractBaseClass.class, cache);

runner.runChild(ArchUnitRunnerTestUtils.getRule(AbstractBaseClass.INSTANCE_FIELD_NAME, runner), runNotifier);

verifyTestFinishedSuccessfully(AbstractBaseClass.INSTANCE_FIELD_NAME);
}

@Test
Expand Down Expand Up @@ -159,6 +162,18 @@ private ArchTestExecution getRule(String name) {
return ArchUnitRunnerTestUtils.getRule(name, runner);
}

private void verifyTestFinishedSuccessfully(String expectedDescriptionMethodName) {
verifyTestFinishedSuccessfully(runNotifier, descriptionCaptor, expectedDescriptionMethodName);
}

static void verifyTestFinishedSuccessfully(RunNotifier runNotifier, ArgumentCaptor<Description> descriptionCaptor,
String expectedDescriptionMethodName) {
verify(runNotifier, never()).fireTestFailure(any(Failure.class));
verify(runNotifier).fireTestFinished(descriptionCaptor.capture());
Description description = descriptionCaptor.getValue();
assertThat(description.getMethodName()).isEqualTo(expectedDescriptionMethodName);
}

@AnalyzeClasses(packages = "some.pkg")
public static class SomeArchTest {
static final String SATISFIED_FIELD_NAME = "someSatisfiedRule";
Expand All @@ -184,6 +199,17 @@ public static class ArchTestWithPrivateInstanceField {
private ArchRule privateField = all(classes()).should(BE_SATISFIED);
}

@AnalyzeClasses(packages = "some.pkg")
public static class ArchTestWithAbstractBaseClass extends AbstractBaseClass {
}

abstract static class AbstractBaseClass {
static final String INSTANCE_FIELD_NAME = "abstractBaseClassInstanceField";

@ArchTest
ArchRule abstractBaseClassInstanceField = all(classes()).should(BE_SATISFIED);
}

@AnalyzeClasses(packages = "some.pkg")
public static class WrongArchTestWrongFieldType {
static final String NO_RULE_AT_ALL_FIELD_NAME = "noRuleAtAll";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,28 @@ public void ignores_double_nested_method_rule_in_ignored_rule_set() {
run(someMethodRuleName, runnerForIgnoredRuleLibrary, verifyTestIgnored());
}

@Test
public void should_allow_ArchRules_in_class_with_instance_field_in_abstract_base_class() {
ArchUnitRunner runner = newRunnerFor(ArchTestWithRulesWithAbstractBaseClass.class, cache);

runner.runChild(getRule(ArchUnitRunnerRunsRuleFieldsTest.AbstractBaseClass.INSTANCE_FIELD_NAME, runner), runNotifier);

verifyTestFinishedSuccessfully(ArchUnitRunnerRunsRuleFieldsTest.AbstractBaseClass.INSTANCE_FIELD_NAME);
}

@Test
public void should_allow_ArchRules_in_class_with_instance_method_in_abstract_base_class() {
ArchUnitRunner runner = newRunnerFor(ArchTestWithRulesWithAbstractBaseClass.class, cache);

runner.runChild(getRule(ArchUnitRunnerRunsMethodsTest.AbstractBaseClass.INSTANCE_METHOD_NAME, runner), runNotifier);

verifyTestFinishedSuccessfully(ArchUnitRunnerRunsMethodsTest.AbstractBaseClass.INSTANCE_METHOD_NAME);
}

private void verifyTestFinishedSuccessfully(String expectedDescriptionMethodName) {
ArchUnitRunnerRunsRuleFieldsTest.verifyTestFinishedSuccessfully(runNotifier, descriptionCaptor, expectedDescriptionMethodName);
}

private Runnable verifyTestIgnored() {
return new Runnable() {
@Override
Expand Down Expand Up @@ -304,4 +326,12 @@ public void check(JavaClass item, ConditionEvents events) {
}
};
}

@AnalyzeClasses(packages = "some.pkg")
public static class ArchTestWithRulesWithAbstractBaseClass {
@ArchTest
ArchRules fieldRules = ArchRules.in(ArchUnitRunnerRunsRuleFieldsTest.ArchTestWithAbstractBaseClass.class);
@ArchTest
ArchRules methodRules = ArchRules.in(ArchUnitRunnerRunsMethodsTest.ArchTestWithAbstractBaseClass.class);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ private static void resolveChildren(
}

private static <T> T getValue(Field field) {
return getValueOrThrowException(field, WRAP_CAUSE);
return getValueOrThrowException(field, field.getDeclaringClass(), WRAP_CAUSE);
}

private static void resolveArchRules(
Expand Down Expand Up @@ -188,7 +188,7 @@ public Type getType() {

@Override
public ArchUnitEngineExecutionContext execute(ArchUnitEngineExecutionContext context, DynamicTestExecutor dynamicTestExecutor) {
invokeMethod(method, classes.get());
invokeMethod(method, method.getDeclaringClass(), classes.get());
return context;
}
}
Expand Down
Loading