I am trying to write a unit test for an extended AEM core component - a 'button' with an extra field. I use the delegation pattern, and Lombok to reduce implementation code.
My unit test is failing when attempting to get the button ID (inherited from the button super-type) - with a null reference exception - because 'button' is null.
Why would that be? Have I set up my unit test incorrectly? Or could it be that I have used the delegation pattern for the core component incorrectly?
I initially posted this on stackoverflow and received a comment around the 'experimental' use of lombok @Delegate, but to be clear, without Lombok, my unit test would still fail with 'button' being null - so 'its not that'.
I've seen a separate conversation here - where I've tried to follow information from there - but I am still having issues;
https://groups.google.com/g/wcm-io-dev/c/CoBeoVJvdmo/m/ZF6ay7mEBAAJ
Any help would be hugely appreciated.
INTERFACE:
@ProviderType
public interface ExtendedButton extends Button {
String RESOURCE_TYPE = "myproject/components/extendedbutton";
String getVariant();
}
IMPL:
@Model(
adaptables = { Resource.class, SlingHttpServletRequest.class },
adapters = { ExtendedButton.class, Button.class, ComponentExporter.class },
resourceType = ExtendedButton.RESOURCE_TYPE,
defaultInjectionStrategy = DefaultInjectionStrategy.OPTIONAL
)
@Exporter(name = ExporterConstants.SLING_MODEL_EXPORTER_NAME, extensions = ExporterConstants.SLING_MODEL_EXTENSION)
public class ExtendedButtonImpl implements ExtendedButton {
@Delegate
@Self
@Via(type = ResourceSuperType.class)
private Button button;
@ValueMapValue
@Getter
private String variant;
// EXAMPLE
// without lombok, the getter for button ID would be;
public String getId() {
return (null != button) ? button.getId() : null;
}
}
UNIT TEST CODE:
@ExtendWith(AemContextExtension.class)
class ExtendedButtonModelTest {
private final AemContext context = new AemContextBuilder()
.plugin(CORE_COMPONENTS)
.build();
private ExtendedButton model;
@BeforeEach
public void setup() {
context.create().resource("/apps/myproject/components/extendedbutton",
PROPERTY_RESOURCE_SUPER_TYPE, "core/wcm/components/button/v2/button");
Page page = context.create().page("/content/test-page");
context.currentResource(context.create().resource(page, "extendedbutton",
PROPERTY_RESOURCE_TYPE, ExtendedButton.RESOURCE_TYPE,
JCR_TITLE, "button text",
"variant", "light",
"id", "button id",
"linkURL", "https://google.com",
"linkTarget", "_blank",
"accessibilityLabel", "button label"
));
model = context.request().adaptTo(ExtendedButton.class);
}
// UNIT TEST SUCCEEDS
@Test
void testGetVariant() {
String val = model.getVariant();
assertNotNull(val);
assertEquals("light", val);
}
// UNIT TEST THROWS NULL POINTER EXCEPTION ON MODEL
@Test
void testGetButtonId() {
String val = model.getId();
assertNotNull(val);
assertEquals("button-id", val);
}
}
hello john.
in general your code looks correct and you followed basically the correct steps to test with delegation pattern. i’m not a fan of lombok, good that you already did test without it.
you have defined defaultInjectionStrategy = DefaultInjectionStrategy.OPTIONAL, which also makes your “button” variable optional. but you want it to succeed. i would recommend to declare the injection for this as mandatory and then have a look at the stack trace that should be generated during the unit tests when the models impl tries to inject the delegated instance. if this does not help please post the stack trace that is generated after this change.
stefan