AEM Unit test - null exception on extended core component (delegate pattern)

721 views
Skip to first unread message

John Smith

unread,
Aug 25, 2022, 6:00:54 PM8/25/22
to wcm-io Developers

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);
      }
}

John Smith

unread,
Aug 25, 2022, 6:04:55 PM8/25/22
to wcm-io Developers
For more detail; I can instead 'adaptTo(Button.class)' in my unit test, and that would succeed;

Button button = context.request().adaptTo(Button.class);
String val = button.getId();
assertNotNull(val);

However, this approach I feel is 'not correct', and does not give me 100% code coverage on the model, since (I guess?) I'm in this case never using the 'private Button button' variable.

Stefan Seifert

unread,
Aug 26, 2022, 7:08:12 AM8/26/22
to wcm-i...@googlegroups.com

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

John Smith

unread,
Aug 26, 2022, 10:21:29 PM8/26/22
to wcm-io Developers
Ouch!! This turned out to be a heartbreakingly simple issue, considering the time it took to find it...!!

In these two lines of code in my unit test;
context.create().resource("/apps/myproject/components/extendedbutton", PROPERTY_RESOURCE_SUPER_TYPE, "core/wcm/components/button/v2/button");
context.currentResource(context.create().resource(page, "extendedbutton", PROPERTY_RESOURCE_TYPE ... );

I use PROPERTY_RESOURCE_SUPER_TYPE and PROPERTY_RESOURCE_TYPE constants - these are incorrect - since they are "resourceSuperType" and "resourceType" respectively - which don't have the requisite "sling:" prefix.
The constants I needed to use, were;

org.apache.sling.jcr.resource.api.JcrResourceConstants.SLING_RESOURCE_SUPER_TYPE_PROPERTY = "sling:resourceSuperType"
org.apache.sling.jcr.resource.api.JcrResourceConstants.SLING_RESOURCE_TYPE_PROPERTY = "sling:resourceType"

We should use the constants to pass linting/code-quality rules in AMS CI/CD - but sometimes easy to mix up the correct constants to use!!

Thank you Stefan for the guidance; and helping me find the issue.
Reply all
Reply to author
Forward
0 new messages