I have a class that looks like this:
public class A {
private B b1 = new B();
private C c = new C();
private B b2;
}
I want to write an ArchUnit test that finds all the fields in class A that are of type B and that are initialized. So in this case, just field 'b1'.
I wrote a test like this, but can't finish it. Is this even possible?
@ArchTest
void dontInitialize(JavaClasses classesToTest) {
noFields().that().haveRawType(DescribedPredicate.describe("that is a B object", type -> {
return type.isAssignableTo(B.class);
})).should(new ArchCondition<JavaField>("") {
@Override
public void check(JavaField javaField, ConditionEvents conditionEvents) {
// ???
}
});
}
At the byte code level, field initialization is moved into the constructor.
For your example class A
:
private B b1;
descriptor: LB;
flags: (0x0002) ACC_PRIVATE
private C c;
descriptor: LC;
flags: (0x0002) ACC_PRIVATE
private B b2;
descriptor: LB;
flags: (0x0002) ACC_PRIVATE
public A();
descriptor: ()V
flags: (0x0001) ACC_PUBLIC
Code:
stack=3, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: aload_0
5: new #7 // class B
8: dup
9: invokespecial #9 // Method B."<init>":()V
12: putfield #10 // Field b1:LB;
15: aload_0
16: new #16 // class C
19: dup
20: invokespecial #18 // Method C."<init>":()V
23: putfield #19 // Field c:LC;
26: return
So you can have an ArchRule
that tests for setting accesses from a constructor, e.g.:
fields().should(be(describe("set in a constructor", field ->
field.getAccessesToSelf().stream().anyMatch(access ->
access.getOwner().isConstructor() &&
access.getAccessType() == JavaFieldAccess.AccessType.SET
)
)));
using the following static imports:
import static com.tngtech.archunit.base.DescribedPredicate.describe;
import static com.tngtech.archunit.lang.conditions.ArchConditions.be;
import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.fields;
This simple rule can have false negatives when multiple constructors are involved, e.g.
class A {
int i1;
int i2;
A() {
i1 = 0;
}
A(int i2) {
this.i2 = i2;
}
}
but you can probably work out a smarter rule based on this idea.