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.
Environment:
AEM project archetype 19 (link)
Mockito 2.27.0 (link)
AEM Mocks JUnit 4 2.7.2 (link)
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.
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!
AEM project archetype 19 (link)
Mockito 2.27.0 (link)
AEM Mocks JUnit 4 2.7.2 (link)
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.
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
Sling Model Class: Header.class
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
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());
}
}
Common Questions for Writing Test Code for Sling Models
// 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
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
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());
}
}
Common Questions for Writing Test Code for Sling Models
- 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.
No comments:
Post a Comment
If you have any doubts or questions, please let us know.