I would like to create an ArchUnit rule which checks that a class is not breaking encapsulation by exposing a public field.
The only public field allowed should be a constant, something like public static final int MAX_USERS = 100;
Other fields should not be public
I went to try the following code:
@Test
void fieldShouldAllBePrivateToProtectEncapsulation() {
fields().should().bePrivate().check(importedClasses);
}
This unfortunately does not work.
For instance, it is flagging this class as bad:
enum Device {
IOS,
LAPTOP
How can I write a test in ArchUnit to ensure no fields are public, except constant?
What you need is an extra filter step that removes enum constants from consideration. One way to do this, is as follows:
@Test
void fieldShouldAllBePrivateToProtectEncapsulation() {
fields().that(areNotEnumConstants()).should().bePrivate().check(importedClasses);
}
private DescribedPredicate<? super JavaField> areNotEnumConstants() {
return new DescribedPredicate<JavaField>("are not enum constants") {
@Override
public boolean test(JavaField javaField) {
JavaClass owner = javaField.getOwner();
return !owner.isEnum() || !javaField.getRawType().isAssignableTo(owner.reflect());
}
};
}
What this does is check the owning class of each field under consideration. If the field's owner (the declaring class) is an enum, then it checks if the field is assignable to the enum type in question.
This should rule out all enum constants, but has a slight side effect that it also allows the following:
enum Device {
IOS,
LAPTOP;
public Device whatAreYouEvenDoing; // This line will not trip the unit test
}
If you want to allow static final fields to have different modifiers as well, the check becomes:
@Test
void fieldShouldAllBePrivateToProtectEncapsulation() {
fields().that(areNotEnumConstants())
.and().areNotStatic()
.and().areNotFinal()
.should().bePrivate()
.check(importedClasses);
}