This article will demonstrate how to write AEM Unit tests for @SlingServletResourceTypes (OSGi DS 1.4 (R7) component property type annotations) 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.
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!
Using OSGI R7 annotations for your AEM project requires some additional dependencies. Check out Adobe’s guide to enable OSGI R7 annotations.
Test Framework 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 Servlet Resource Types Class : SlingServletResourceTypes.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 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 | package com.sourcedcode.core.servlets; import com.day.cq.wcm.api.Page; import com.fasterxml.jackson.databind.ObjectMapper; import org.apache.sling.api.SlingHttpServletRequest; import org.apache.sling.api.SlingHttpServletResponse; import org.apache.sling.api.resource.Resource; import org.apache.sling.api.resource.ResourceResolver; import org.apache.sling.api.servlets.SlingSafeMethodsServlet; import org.apache.sling.servlets.annotations.SlingServletResourceTypes; import org.osgi.service.component.annotations.Component; import javax.servlet.Servlet; import java.io.IOException; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import static com.day.crx.packaging.JSONResponse.APPLICATION_JSON_UTF8; import static org.apache.sling.api.servlets.HttpConstants.METHOD_GET; /** * A demonstration sling servlet resource type class to demonstrate unit testing. * Appending ".example.json" on any resource will activate the doGet() method below, and return Json data. * Json Data returned is data from the immediate children of the "/content/we-retail', parent resource node. */ @Component(service = Servlet.class) @SlingServletResourceTypes( resourceTypes = "sling/servlet/default", methods = METHOD_GET, extensions = "json", selectors = "example") public class SlingServletResourceTypesExampleDS14Servlet extends SlingSafeMethodsServlet { @Override protected void doGet(SlingHttpServletRequest req, SlingHttpServletResponse res) throws IOException { res.setContentType(APPLICATION_JSON_UTF8); res.setStatus(SlingHttpServletResponse.SC_OK); List<PageItem> pageItems = getPageItems(req); String json = new ObjectMapper().writeValueAsString(pageItems); res.getWriter().write(json); } private List<PageItem> getPageItems(SlingHttpServletRequest req) { List<PageItem> pageItems = new ArrayList<>(); ResourceResolver resolver = req.getResourceResolver(); Resource weRetail = resolver.getResource("/content/we-retail"); if (weRetail != null) { Iterator<Resource> it = weRetail.listChildren(); while (it.hasNext()) { Resource resource = it.next(); if (resource.getResourceType().equalsIgnoreCase(com.day.cq.wcm.api.NameConstants.NT_PAGE)) { Page page = resource.adaptTo(Page.class); if (page != null) { pageItems.add((new PageItem(page.getTitle(), resource.getPath()))); } } } } return pageItems; } /** * Inner class PageItem, for creating PageItem objects. */ public class PageItem { private String pageTitle; private String pagePath; PageItem(String pageTitle, String pagePath) { this.pageTitle = pageTitle; this.pagePath = pagePath; } public String getPageTitle() { return pageTitle; } public String getPagePath() { return pagePath; } } } |
Results of the output from the working as expected SlingServletResourceTypesTest.class.
Sling Servlet Resource Types Test Class : SlingServletResourceTypesTest.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 70 71 72 73 74 75 76 77 78 79 80 81 82 | package com.sourcedcode.core.servlets; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import io.wcm.testing.mock.aem.junit.AemContext; import org.apache.sling.testing.mock.sling.ResourceResolverType; import org.apache.sling.testing.mock.sling.servlet.MockSlingHttpServletRequest; import org.apache.sling.testing.mock.sling.servlet.MockSlingHttpServletResponse; import org.junit.Before; 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 java.io.IOException; import static com.day.crx.packaging.JSONResponse.APPLICATION_JSON_UTF8; import static junitx.framework.Assert.assertEquals; import static org.junit.Assert.assertTrue; @RunWith(MockitoJUnitRunner.class) public class SlingServletResourceTypesExampleDS14ServletTest { @Rule public final AemContext context = new AemContext(ResourceResolverType.JCR_MOCK); @Mock private MockSlingHttpServletRequest req; @Mock private MockSlingHttpServletResponse res; @InjectMocks private SlingServletResourceTypesExampleDS14Servlet underTest; @Before public void setup() { underTest = new SlingServletResourceTypesExampleDS14Servlet(); req = context.request(); res = context.response(); } @Test public void doGet_shouldReturnHeaderAsExpected() throws IOException { underTest.doGet(req, res); assertEquals(res.getContentType(), APPLICATION_JSON_UTF8); } @Test public void doGet_shouldReturnPageItemListJsonAsExpected_0() throws IOException { underTest.doGet(req, res); String jsonString = res.getOutputAsString(); ObjectMapper mapper = new ObjectMapper(); JsonNode actualObj = mapper.readTree(jsonString); assertEquals(0, actualObj.size()); } @Test public void doGet_shouldReturnPageItemJsonAsExpected_properties_values() throws IOException { createPagesInJcrMock(); underTest.doGet(req, res); String jsonString = res.getOutputAsString(); ObjectMapper mapper = new ObjectMapper(); JsonNode actualObj = mapper.readTree(jsonString); JsonNode firstItem = actualObj.get(0); assertTrue(firstItem.has("pageTitle")); assertTrue(firstItem.has("pagePath")); assertEquals( "United states", firstItem.get("pageTitle").textValue()); assertEquals("/content/we-retail/us", firstItem.get("pagePath").textValue()); } /** * Test helper method to create 3 pages in the on-memory JCR Mock instance. */ private void createPagesInJcrMock() { context.create().page("/content/we-retail", "/", "We Retail"); context.create().page("/content/we-retail/us", "/", "United states"); context.create().page("/content/we-retail/ca", "/", "Canada"); } } |
Though the endpoint path is ugly (because it consists of extensions, selectors, and etc…), it is recomended to register servlet by resource types rather than by path (click here to learn why @SlingServletPaths, register by path, are not recommended).
If there is a requirement to mask or to sugarcoat the ugly URI paths, a common strategy used is the Apache webserver. Click here to learn to sugar coat registered AEM servlet scripts and paths endpoint.
Really great example! Thank you!