How to write Unit Tests for Extended Core Components' Java Models | Community
Skip to main content
Level 3
November 2, 2023

How to write Unit Tests for Extended Core Components' Java Models

  • November 2, 2023
  • 2 replies
  • 2251 views

I extended the Text Core Component's Java Class with a simple "moreText" variable. 

 

But I am still using some of the Core Component's methods in the HTL so I need them in my Java Class. 

 

How can I Unit Test the Extended Core Component's methods that require the ResourceSuperType?

 

package com.adobe.aem.guides.wknd.core.models; import com.adobe.cq.export.json.ComponentExporter; import com.adobe.cq.export.json.ExporterConstants; import com.adobe.cq.wcm.core.components.models.Text; import lombok.Getter; import org.apache.sling.api.SlingHttpServletRequest; import org.apache.sling.models.annotations.Default; import org.apache.sling.models.annotations.DefaultInjectionStrategy; import org.apache.sling.models.annotations.Exporter; import org.apache.sling.models.annotations.Model; import org.apache.sling.models.annotations.Via; import org.apache.sling.models.annotations.injectorspecific.Self; import org.apache.sling.models.annotations.injectorspecific.ValueMapValue; import org.apache.sling.models.annotations.via.ResourceSuperType; @Model( adaptables = SlingHttpServletRequest.class, adapters = {ComponentExporter.class}, resourceType = ExtendedCoreComponentModel.RESOURCE_TYPE, defaultInjectionStrategy = DefaultInjectionStrategy.OPTIONAL ) @Exporter( name = ExporterConstants.SLING_MODEL_EXPORTER_NAME, extensions = ExporterConstants.SLING_MODEL_EXTENSION ) public class ExtendedCoreComponentModel implements Text { static final String RESOURCE_TYPE = "wknd/components/extendedcorecomponent"; @Self @2434638(type = ResourceSuperType.class) protected Text text; @14766979 @ValueMapValue @1497330(values = "Hello World.") private String moreText; @9944223 public String getText() { return text.getText(); } @9944223 public boolean isRichText() { return text.isRichText(); } }

 

This post is no longer active and is closed to new replies. Need help? Start a new post to ask your question.

2 replies

Level 3
November 6, 2023

Thanks for the help but this is not a solution. I saw these answers you mention before posting this question. 

 

The first Q&A uses ...

FieldSetter.setField()

 ... but this is a deprecated method and field. 

 

The second Q&A literally mentions they cannot test overridden methods. 

 

The code you share in your repo also avoids unit testing an overridden method (or a ResourceSuperType). 

Sady_Rifat
Community Advisor
Community Advisor
November 3, 2023

Hello @jeremylanssiers ,

You can follow this article where each step is described: https://medium.com/@sadyrifat/aem-unit-test-case-for-sling-delegation-pattern-1f011b947fb3 

Another GitHub commit link and repo for check your maven dependency also: https://github.com/Sady-Rifat/aem-demo/commit/81ce47fc28b70d31bee0034e2cadf69b721d892e 

Hope this will help you.

Level 3
November 10, 2023

I have not been able to test your solution yet. However I was able to test a ResourceSuperType by directly injecting a mock value. E.g.

 

@Mock

CoreComponent coreComponent;

 

ExtendedCoreComponent extendedCoreComponent = new ExtendedCoreComponent();

 

extendedCoreComponent.coreComponent =coreComponent;

 

The obvious drawback is I had to make that coreComponent protected instead of private in my ExtendedCoreComponent class.

 

 

Level 3
December 26, 2023

I was able to find a solution looking at the Unit test for WKND project's ImageList at 

https://github.com/adobe/aem-guides-wknd/blob/main/core/src/test/java/com/adobe/aem/guides/wknd/core/models/impl/ImageListImplTest.java

The magic happens at these lines:

 

Field reader = ExtendedCoreComponentImpl.class.getDeclaredField("text");
reader.setAccessible(true);
reader.set(extendedCoreComponentImpl, text);


Class

 

package com.adobe.aem.guides.wknd.core.models.impl; import com.adobe.aem.guides.wknd.core.models.ExtendedCoreComponent; import com.adobe.cq.export.json.ComponentExporter; import com.adobe.cq.export.json.ExporterConstants; import com.adobe.cq.wcm.core.components.models.Text; import org.apache.sling.api.SlingHttpServletRequest; import org.apache.sling.models.annotations.Default; import org.apache.sling.models.annotations.DefaultInjectionStrategy; import org.apache.sling.models.annotations.Exporter; import org.apache.sling.models.annotations.Model; import org.apache.sling.models.annotations.Via; import org.apache.sling.models.annotations.injectorspecific.Self; import org.apache.sling.models.annotations.injectorspecific.ValueMapValue; import org.apache.sling.models.annotations.via.ResourceSuperType; @Model( adaptables = SlingHttpServletRequest.class, adapters = { ExtendedCoreComponent.class,ComponentExporter.class}, resourceType = ExtendedCoreComponentImpl.RESOURCE_TYPE, defaultInjectionStrategy = DefaultInjectionStrategy.OPTIONAL ) @Exporter(name = ExporterConstants.SLING_MODEL_EXPORTER_NAME, extensions = ExporterConstants.SLING_MODEL_EXTENSION) public class ExtendedCoreComponentImpl implements ExtendedCoreComponent { static final String RESOURCE_TYPE = "wknd/components/extendedcorecomponent"; @Self (type = ResourceSuperType.class) private Text text; @ValueMapValue (values="Hello World.") private String moreText; public String getMoreText() { return moreText; }; public String getText() { return text.getText(); } public boolean isRichText() { return text.isRichText(); } public String getExportedType() { return ExtendedCoreComponentImpl.RESOURCE_TYPE; } }

 

 

Mock data

 

{ "jcr:primaryType": "nt:unstructured", "moreText": "moreText", "id": "id", "text": "<p>text</p>", "sling:resourceType": "wknd/components/extendedcorecomponent", "textIsRich": "true" }

 

 

Unit Test

 

package com.adobe.aem.guides.wknd.core.models.impl; import com.adobe.aem.guides.wknd.core.utils.TestUtil; import com.adobe.cq.wcm.core.components.models.Text; import io.wcm.testing.mock.aem.junit5.AemContext; import io.wcm.testing.mock.aem.junit5.AemContextExtension; import org.apache.sling.testing.mock.sling.ResourceResolverType; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; import java.lang.reflect.Field; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.Mockito.when; @ExtendWith({AemContextExtension.class, MockitoExtension.class}) class ExtendedCoreComponentImplTest { private final AemContext aemContext = new AemContext(ResourceResolverType.JCR_MOCK); private static final String JCR_PATH = TestUtil.getMockJcrPath("extendedCoreComponent"); private static final String MOCK_DATA = "/components/extendedCoreComponent/extendedCoreComponent.json"; private static final String MOCK_DATA_EMPTY = "/components/extendedCoreComponent/extendedCoreComponent-empty.json"; @Mock private Text text; @InjectMocks private ExtendedCoreComponentImpl extendedCoreComponentImpl; @BeforeEach void setup() { aemContext.addModelsForPackage(TestUtil.CORE_PACKAGES); } @Nested class ExtendedComponent { @BeforeEach void setUp() { aemContext.load().json("/pages/pages.json", "/content"); } void emptyComponent() { extendedCoreComponentImpl = TestUtil.loadSlingModelViaSlingHttpServletRequest(aemContext, ExtendedCoreComponentImpl.class, MOCK_DATA_EMPTY, JCR_PATH); assertEquals("Hello World.", extendedCoreComponentImpl.getMoreText()); } void component() { extendedCoreComponentImpl = TestUtil.loadSlingModelViaSlingHttpServletRequest(aemContext, ExtendedCoreComponentImpl.class, MOCK_DATA, JCR_PATH); assertEquals("moreText", extendedCoreComponentImpl.getMoreText()); } } @Nested class CoreComponent { @BeforeEach void setup() { aemContext.load().json(MOCK_DATA, "/content"); aemContext.addModelsForClasses(Text.class); } void coreComponent() throws NoSuchFieldException, IllegalAccessException { extendedCoreComponentImpl = TestUtil.loadSlingModelViaSlingHttpServletRequest(aemContext, ExtendedCoreComponentImpl.class, MOCK_DATA, JCR_PATH); Field reader = ExtendedCoreComponentImpl.class.getDeclaredField("text"); reader.setAccessible(true); reader.set(extendedCoreComponentImpl, text); when(text.isRichText()).thenReturn(true); when(text.getText()).thenReturn("abc"); assertTrue(extendedCoreComponentImpl.isRichText()); assertEquals("abc", extendedCoreComponentImpl.getText()); } } }

 


Test Util Class

 

package com.adobe.aem.guides.wknd.core.utils; import io.wcm.testing.mock.aem.junit5.AemContext; import org.apache.commons.lang3.StringUtils; import org.apache.sling.api.SlingHttpServletRequest; import org.apache.sling.api.resource.Resource; import org.apache.sling.models.annotations.Model; import org.apache.sling.models.factory.ModelFactory; import org.apache.sling.testing.mock.sling.servlet.MockSlingHttpServletRequest; public class TestUtil { public static final String CORE_PACKAGES = "com.adobe.aem.guides.wknd.core"; private static final String JCR_PAGE_PATH = "/content/project/country/language/page/jcr:content/root/container/container/%s"; public static String getMockJcrPath(String componentName) { return String.format(JCR_PAGE_PATH, componentName); } public static <T> T loadSlingModel(AemContext aemContext, Class<T> clazz, String mockDataPath, String mockJcrPath) { boolean isAdaptableFromResource = false; boolean isAdaptableFromSlingHttpServletRequest = false; Class<?>[] adaptables = clazz.getAnnotation(Model.class).adaptables(); for (Class<?> adaptable : adaptables) { if (StringUtils.equals(adaptable.getName(), Resource.class.getName())) { isAdaptableFromResource = true; } if (StringUtils.equals(adaptable.getName(), SlingHttpServletRequest.class.getName())) { isAdaptableFromSlingHttpServletRequest = true; } } if (isAdaptableFromResource) { return loadSlingModelViaResource(aemContext, clazz, mockDataPath, mockJcrPath); } if (isAdaptableFromSlingHttpServletRequest) { return loadSlingModelViaSlingHttpServletRequest(aemContext, clazz, mockDataPath, mockJcrPath); } return null; } public static <T> T loadSlingModelViaSlingHttpServletRequest(AemContext aemContext, Class<T> clazz, String mockDataPath, String mockJcrPath) { aemContext.load().json(mockDataPath, mockJcrPath); Resource resource = aemContext.resourceResolver().getResource(mockJcrPath); MockSlingHttpServletRequest request = aemContext.request(); request.setResource(resource); ModelFactory modelFactory = aemContext.getService(ModelFactory.class); return modelFactory.createModel(request, clazz); } public static <T> T loadSlingModelViaResource(AemContext aemContext, Class<T> clazz, String mockDataPath, String mockJcrPath) { aemContext.load().json(mockDataPath, mockJcrPath); Resource resource = aemContext.resourceResolver().getResource(mockJcrPath); ModelFactory modelFactory = aemContext.getService(ModelFactory.class); return modelFactory.createModel(resource, clazz); } }