Creating new AEM components, we sometimes need backend logic to compute user requests with business logic. There are multiple ways of doing so, like using the Java-Use API or Javascript-Use API, but the most popular and best practice of writing business logic for an AEM component will be using Sling Models.
This article will demonstrate how to write AEM Unit tests for sling models using the Junit4 testing framework. With developers being more visual, the source code is posted below.
This example uses the AEM project archetype 19 to generate a new AEM project, Junit 4 will be used as the testing framework, Mockito 2.27.0 will be used as the mocking framework, and AEM Mocks will be used to mock AEM objects and AEM API’s.
What’s really great about the latest versions of AEM mocks, is that the setup is very minimal. After spinning up a new AEM project from the AEM project archetype 19, you simply need to include the AEM Mocks dependency, and you are ready to go!
Dependencies
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | // pom.xml <!-- Maven Surefire Plugin --> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-surefire-plugin</artifactId> <version>2.20</version> <configuration> <junitArtifactName>junit:junit:4</junitArtifactName> </configuration> </plugin> ... <dependencies> ... <dependency> <groupId>io.wcm</groupId> <artifactId>io.wcm.testing.aem-mock</artifactId> <version>2.7.2</version> <scope>test</scope> </dependency> ... </dependencies> // core/pom.xml <dependencies> ... <dependency> <groupId>io.wcm</groupId> <artifactId>io.wcm.testing.aem-mock</artifactId> </dependency> ... </dependencies> |
Sling Model Class : Header.class
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 | package com.sourcedcode.core.models; import com.day.cq.wcm.api.Page; import org.apache.sling.api.resource.Resource; import org.apache.sling.api.resource.ResourceResolver; import org.apache.sling.models.annotations.DefaultInjectionStrategy; import org.apache.sling.models.annotations.Model; import org.apache.sling.models.annotations.injectorspecific.ChildResource; import org.apache.sling.models.annotations.injectorspecific.ScriptVariable; import org.apache.sling.models.annotations.injectorspecific.ValueMapValue; import javax.annotation.PostConstruct; import javax.inject.Inject; @Model(adaptables = Resource.class, resourceType = Header.RESOURCE_TYPE, defaultInjectionStrategy = DefaultInjectionStrategy.OPTIONAL) public class Header { protected static final String RESOURCE_TYPE = "sourcedcode/components/structure/header"; @ValueMapValue private String contactUsPath; @ScriptVariable(name="currentPage") private Page currentPage; @ChildResource(name="link") Resource childResource; @SlingObject private ResourceResolver resolver; private String contactUsPageSecretChar; @PostConstruct public void init() { setContactUsPageSecretChar(); } private void setContactUsPageSecretChar() { Resource resource = resolver.getResource(contactUsPath); if (resource != null) { Page contactUsPage = resource.adaptTo(Page.class); contactUsPageSecretChar = contactUsPage.getTitle(); } } public String getContactUsPageSecretChar() { return contactUsPageSecretChar.substring(contactUsPageSecretChar.length() - 1); } // demo of testing the @ScriptVariable("currentPage") annotation public String getPageTitle() { return currentPage.getPageTitle(); } // demo of testing the @ChildResource annotation public String getChildLinkPropFlag() { return childResource.getValueMap().get("flag", ""); } } |
Sling Model Test Class : HeaderTest.class
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 | package com.sourcedcode.core.models; import com.adobe.cq.commerce.common.ValueMapDecorator; import com.day.cq.wcm.api.Page; import com.google.common.collect.ImmutableMap; import io.wcm.testing.mock.aem.junit.AemContext; import org.apache.sling.api.resource.Resource; import org.apache.sling.testing.mock.sling.ResourceResolverType; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; import static junitx.framework.Assert.assertEquals; import static org.mockito.Mockito.when; @RunWith(MockitoJUnitRunner.class) public class HeaderTest { @Rule public final AemContext context = new AemContext(ResourceResolverType.JCR_MOCK); // the context.resourceResolver() is auto injected by the AemContext, cannot be mocked. // ResourceResolver resolver; // mocking the global AEM object "currentPage". // variable does not need to match the variables in the underTest.class @Mock private Page currentPage; // injects all the mocks into the tested object. @InjectMocks private Header underTest; @Test public void itShouldReturnTheCorrectSecretCharWhenResourceExist() { // using the AEM context to create an AEM resource in the context, to set properties for the resource. // the resource path can be anything made up. Resource headerResourceContext = context.create().resource("/content/sourcedcode/home/jcr:content/header", new ValueMapDecorator(ImmutableMap.<String, Object> of( "contactUsPath", "/content/sourcedcode/contact", "anotherProperty", "example"))); // create mock page, resolved by the resolver. context.create().page("/content/sourcedcode/contact", "", ImmutableMap.<String, Object>builder() .put("jcr:title", "Contact Us Page") .build()); underTest = headerResourceContext.adaptTo(Header.class); assertEquals("e", underTest.getContactUsPageSecretChar()); } @Test public void itShouldReturnTheCorrectCurrentPageTitle() { when(currentPage.getPageTitle()).thenReturn("Home Page"); assertEquals("Home Page", underTest.getPageTitle()); } @Test public void itShouldReturnTheCorrectChildLinkProperty() { context.build().resource("/content/sourcedcode/home/jcr:content/header") .siblingsMode() .resource("link", "flag", "newPage"); underTest = context.resourceResolver().getResource("/content/sourcedcode/home/jcr:content/header").adaptTo(Header.class); assertEquals("newPage", underTest.getChildLinkPropFlag()); } } |
- How do I initial properties in my sling model object? First, ensure that your sling model allows a resource.class to be adaptable, then in your sling model test class, create a mockResource object, setup up the mockResource object, and adapt to the sling model class that you are trying to test.
- Which Context should I be used when testing for sling models? You should use the JCR_MOCK context.
For AEM Sling Servlet by Resource Type, Unit Test Example Using wcm.io AEM Mocks, click here.
This was exactly what I was looking for. Can you please write a post on how to implemenet a simple navigation component, with some unit tests?
Awsome, thanks!
How about Junit5? Please provide some examples?
You’re a legend!
Thank you, this was a good reference!