December 28, 2020
Estimated Post Reading Time ~

AEM Sling Models Unit Test Example Using wcm.io AEM Mocks

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.

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!

Dependencies
// 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.


By aem4beginner

No comments:

Post a Comment

If you have any doubts or questions, please let us know.