JUnit 4: 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.

Technologies here used are:

  • 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’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());
    }

}
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.

For AEM Sling Servlet by Resource Type, Unit Test Example Using wcm.io AEM Mocks, click here.




Was this post helpful?

Certified AEM Developer who has been working on AEM software developer for the past 5 years.

4 thoughts on “JUnit 4: AEM Sling Models Unit Test Example Using wcm.io AEM Mocks

  1. 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?

Leave a Reply

Your email address will not be published. Required fields are marked *

Back To Top