Unit Test for Sling Model Delegation Pattern

The Sling Model Delegation pattern allows extending Core Components functionality while maintaining the original API contract. In a previous article, we discussed how to implement Sling Model delegation pattern. Now, we will cover how to write comprehensive unit tests and ensure code coverage for these delegated Sling Models using JUnit 5, AEM Mocks, and proper test setup.
For this tutorial, we will use an example Button component that extends the Core WCM Components Button using the delegation pattern. We'll demonstrate how to properly test both the delegated functionality and any custom overridden methods.
First, let's examine the test content structure. We need to set up proper JSON files that represent both the component definitions and the content structure for testing.
content / aem-demo / components / button.json
{ "button": { "jcr:primaryType": "nt:unstructured", "jcr:title": "Discussion Guide", "textColor": "text-primary", "backgroundColor": "bg-primary", "icon": "adobe", "sling:resourceType": "aem-demo/components/button" } }
The component definition files are essential for the delegation pattern to work correctly in unit tests. These JSON files establish the component hierarchy and resource type relationships that AEM Mocks needs to resolve thesling:resourceSuperType delegation chain. Without proper component definitions, the @Via(type = ResourceSuperType.class) injection will fail, causing test failures.
apps / aem-demo / components / button.json
{ "button": { "jcr:primaryType": "cq:Component", "sling:resourceSuperType": "core/wcm/components/button/v2/button", "componentGroup": "AEM Demo - Content", "jcr:title": "Button", "sling:resourceType": "aem-demo/components/button" } }
apps / core /components / button.json
{ "button": { "jcr:primaryType": "cq:Component", "componentGroup": "Core WCM Components", "jcr:title": "Button", "sling:resourceType": "core/wcm/components/button/v2/button" } }
Here's the Sling Model implementation we'll be testing. This demonstrates the delegation pattern where most functionality is delegated to the Core Component, with specific methods overridden for customization:
ButtonImpl.java
@Model( adaptables = SlingHttpServletRequest.class, adapters = { Button.class }, resourceType = ButtonImpl.RESOURCE_TYPE, defaultInjectionStrategy = DefaultInjectionStrategy.OPTIONAL ) @Exporter( name = ExporterConstants.SLING_MODEL_EXPORTER_NAME, extensions = ExporterConstants.SLING_MODEL_EXTENSION ) public class ButtonImpl implements Button { protected static final String RESOURCE_TYPE = "aem-demo/components/button"; @Self @Via(type = ResourceSuperType.class) @Delegate(excludes = DelegationExclusion.class) private com.adobe.cq.wcm.core.components.models.Button coreButton; @Override public String getText() { // ... custom implementation ... return button.getText(); } @Override public String getExportedType() { return ButtonImpl.RESOURCE_TYPE; } private interface DelegationExclusion { String getText(); String getExportedType(); } }
Now, let's implement comprehensive unit tests using AemContext. The test setup includes loading component definitions, test content, and properly registering the Sling Models for testing.
ButtonImplTest.java
@ExtendWith(AemContextExtension.class) public class ButtonImplTest { private final AemContext context = new AemContextBuilder(ResourceResolverType.JCR_MOCK) .plugin(ContextPlugins.CORE_COMPONENTS) .build(); @BeforeEach public void setUp() { // Load component definitions from JSON context.load().json("/apps/aem-demo/components/button.json", "/apps/aem-demo/components"); context.load().json("/apps/core/components/button.json", "/apps/core/wcm/components/button/v2"); // Load test content context.load().json("/content/aem-demo/components/button.json", "/content"); context.currentResource("/content/button"); // Register all necessary models context.addModelsForClasses(ButtonImpl.class); } @Test public void testButton() { ModelFactory modelFactory = context.getService(ModelFactory.class); Button button = Objects.requireNonNull(modelFactory) .createModel(context.request(), ButtonImpl.class); Assertions.assertNotNull(button); Assertions.assertEquals(ButtonImpl.RESOURCE_TYPE, button.getExportedType()); Assertions.assertEquals("text-primary", button.getTextColor()); Assertions.assertEquals("bg-primary", button.getBackgroundColor()); Assertions.assertEquals("Discussion Guide", button.getText()); } }
By following this testing approach, you can maintain high code coverage and ensure reliability when extending Core Components through the Sling Model delegation pattern.
Write your Comment