JUnit Test in AEM 6.5 – Servlet

How to test a Servlet with AEMContext in AEM

Goal

Test a Servlet using AEMContext. We will extend the Generic Abstract Class created here.

Procedure

Let’s suppose to have this really simple Servlet:

package com.adobe.training.core.servlets;

import com.day.cq.wcm.api.Page;
import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.api.SlingHttpServletResponse;
import org.apache.sling.api.resource.Resource;
import org.apache.sling.api.servlets.HttpConstants;
import org.apache.sling.api.servlets.SlingAllMethodsServlet;
import org.json.JSONException;
import org.json.JSONObject;
import org.osgi.framework.Constants;
import org.osgi.service.component.annotations.Component;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.servlet.Servlet;
import java.io.IOException;

/**
 * SimpleServletForTest Class.
 *
 * @author riccardo.teruzzi
 */
@Component(service = Servlet.class,
        property = {
                Constants.SERVICE_DESCRIPTION + "=Simple Servlet",
                "sling.servlet.methods=" + HttpConstants.METHOD_GET,
                "sling.servlet.resourceTypes=" + "cq:Page",
                "sling.servlet.selectors=" + "test",
                "sling.servlet.extensions=" + "json"
        })
public class SimpleServletForTest extends SlingAllMethodsServlet {

    private static final Logger LOGGER = LoggerFactory.getLogger(SimpleServletForTest.class);

    @Override
    protected void doGet(final SlingHttpServletRequest req,
                         final SlingHttpServletResponse resp) throws IOException {

        Resource resource = req.getResource();
        Page page = resource.adaptTo(Page.class);
        JSONObject jsonObject = new JSONObject();
        if (page != null) {
            try {
                jsonObject.put("pageName", page.getTitle());
                jsonObject.put("navigationTitle", page.getNavigationTitle());
            } catch (JSONException e) {
                LOGGER.error("Failed to create JSON", e);
            }
        }

        resp.getWriter().write(jsonObject.toString());
    }

}

First of all, if you haven’t checked it yet, please read the article here. We will always extend the generic AbstractBaseTest class in our test examples.

In this case I’ve also created another intermediate abstract class, useful for testing servlets. It contains different methods that I’ve always used with this approach, and it could have many others as well if necessary. Here the class:

package com.adobe.training.core.servlets;

import com.adobe.training.core.models.AbstractBaseTest;
import com.day.cq.wcm.api.Page;
import org.apache.sling.api.SlingHttpServletResponse;
import org.apache.sling.api.resource.Resource;
import org.junit.Before;
import org.mockito.Mock;

import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.net.URISyntaxException;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.stream.Collectors;

import static org.mockito.Mockito.when;

/**
 * Created by r.teruzzi on 20/11/2018.
 */
public abstract class AbstractServletTest extends AbstractBaseTest {

    protected Resource resource;

    protected Page page;

    /**
     * The sling response.
     */
    @Mock
    protected SlingHttpServletResponse slingResponse;

    protected PrintWriter writer;

    protected StringWriter stringWriter;

    @Before
    public void setUp() throws NoSuchFieldException {
        super.setUp();
        try {
            stringWriter = new StringWriter();
            writer = new PrintWriter(stringWriter);
            when(slingResponse.getWriter()).thenReturn(writer);
        } catch (IOException e) {
            LOG.error("Failed to return writer", e);
        }
    }

    protected String getJsonFromFile(String path) throws URISyntaxException, IOException {
        ClassLoader loader = ClassLoader.getSystemClassLoader();
        URL resource = loader.getResource(path);
        assertNotNull(resource);
        return Files.lines(Paths.get(resource.toURI()))
                .parallel()
                .collect(Collectors.joining());
    }

    protected void setCurrentResource(String path) {
        Resource pageResource = resourceResolver.getResource(path);
        assertNotNull(pageResource);
        resource = pageResource;
        page = pageResource.adaptTo(Page.class);
        aemContext.currentPage(page);
    }

}

In this case, we are going to initialize the response writer in the setUp method and then there are two additional methods, one to load the expected JSON response, and the other one to set the current resource. You will see in the Servlet Test class how both of them will be used.

And finally, our Java Test class:

package com.adobe.training.core.servlets;

import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.api.resource.Resource;
import org.apache.sling.api.resource.ResourceResolver;
import org.apache.sling.api.wrappers.SlingHttpServletRequestWrapper;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.junit.MockitoJUnitRunner;


import java.io.IOException;
import java.net.URISyntaxException;

/**
 * Created by r.teruzzi
 */
@RunWith(MockitoJUnitRunner.class)
public class SimpleServletForTestTest extends AbstractServletTest {

    public final static String CURRENT_PAGE_PATH = "/content/we-retail/us/en/men";

    @Override
    protected void loadCommonContent() {
        aemContext.load().json("/crxContent/menPage.json", CURRENT_PAGE_PATH);
    }

    @InjectMocks
    private SimpleServletForTest simpleServletForTest;

    private SimpleServletForTestRequest simpleServletForTestRequest;

    @Before
    public void setUp() throws NoSuchFieldException {
        super.setUp();
        aemContext.requestPathInfo().setSelectorString("test");
        simpleServletForTestRequest = new SimpleServletForTestRequest(request);
        setCurrentResource(CURRENT_PAGE_PATH);
    }

    @Test
    public void doGet() throws IOException, URISyntaxException {
        simpleServletForTest.doGet(simpleServletForTestRequest, slingResponse);
        String json = getJsonFromFile("expectedResults/simpleServletForTest.json");
        assertEquals(json, stringWriter.toString());
    }


    private class SimpleServletForTestRequest extends SlingHttpServletRequestWrapper {

        /**
         * Instantiates a new my request.
         *
         * @param wrappedRequest the wrapped request
         */
        public SimpleServletForTestRequest(SlingHttpServletRequest wrappedRequest) {
            super(wrappedRequest);
        }

        /*
         * (non-Javadoc)
         *
         * @see org.apache.sling.api.wrappers.SlingHttpServletRequestWrapper#
         * getResourceResolver()
         */
        @Override
        public ResourceResolver getResourceResolver() {
            return resourceResolver;
        }

        /*
         * (non-Javadoc)
         *
         * @see org.apache.sling.api.wrappers.SlingHttpServletRequestWrapper#
         * getResource()
         */
        @Override
        public Resource getResource() {
            return resource;
        }

    }

}

The code should be clear. In particular. we have

  • the loadCommonContent method that loads a specific JSON for this test
  • the getJsonFromFile method that loads the expected JSON file.
  • the setCurrentResource method to initizialize the resource.
  • the inner class SimpleServletForTestRequest for the Request that will be passed to the servlet.

If you need to execute the test, this is the simpleServletForTest.json file:

{"navigationTitle":"Men Navigation","pageName":"Men"}

And this is the menPage.json file. Yes, it’s a bit long cause it is the full page:

{
  "jcr:primaryType": "cq:Page",
  "jcr:createdBy": "admin",
  "jcr:created": "Sat Oct 24 2020 12:40:02 GMT+0200",
  "jcr:content": {
    "jcr:primaryType": "cq:PageContent",
    "jcr:mixinTypes": [
      "cq:LiveRelationship",
      "cq:PropertyLiveSyncCancelled"
    ],
    "jcr:createdBy": "admin",
    "jcr:title": "Men",
    "cq:propertyInheritanceCancelled": [
      "cq:tags",
      "navTitle"
    ],
    "cq:template": "/conf/we-retail/settings/wcm/templates/hero-page",
    "cq:lastRolledout": "Wed Jun 29 2016 14:20:45 GMT+0200",
    "jcr:created": "Sat Oct 24 2020 12:40:02 GMT+0200",
    "cq:lastModified": "Sat Dec 12 2020 13:12:50 GMT+0100",
    "cq:tags": [
      "we-retail:gender/men",
      "we-retail:equipment"
    ],
    "sling:resourceType": "weretail/components/structure/page",
    "cq:lastRolledoutBy": "admin",
    "cq:lastModifiedBy": "admin",
    "navTitle": "Men Navigation",
    "root": {
      "jcr:primaryType": "nt:unstructured",
      "jcr:mixinTypes": [
        "cq:LiveRelationship"
      ],
      "cq:lastRolledout": "Mon Mar 14 2016 22:18:57 GMT+0100",
      "sling:resourceType": "wcm/foundation/components/responsivegrid",
      "cq:lastRolledoutBy": "admin",
      "hero_image": {
        "jcr:primaryType": "nt:unstructured",
        "jcr:mixinTypes": [
          "cq:LiveRelationship"
        ],
        "jcr:createdBy": "admin",
        "fileReference": "/content/dam/we-retail/en/activities/climbing/indoor-practicing.jpg",
        "jcr:lastModifiedBy": "admin",
        "cq:lastRolledout": "Mon Mar 14 2016 22:18:57 GMT+0100",
        "useFullWidth": "true",
        "jcr:created": "Wed Jan 13 2016 15:58:04 GMT+0100",
        "jcr:lastModified": "Mon Mar 14 2016 22:18:57 GMT+0100",
        "sling:resourceType": "weretail/components/content/heroimage",
        "cq:lastRolledoutBy": "admin"
      },
      "responsivegrid": {
        "jcr:primaryType": "nt:unstructured",
        "jcr:mixinTypes": [
          "cq:LiveRelationship"
        ],
        "jcr:createdBy": "admin",
        "jcr:lastModifiedBy": "admin",
        "cq:lastRolledout": "Mon Mar 14 2016 22:18:57 GMT+0100",
        "jcr:created": "Wed Jan 13 2016 15:58:09 GMT+0100",
        "jcr:lastModified": "Mon Mar 14 2016 22:18:57 GMT+0100",
        "sling:resourceType": "wcm/foundation/components/responsivegrid",
        "cq:lastRolledoutBy": "admin",
        "title": {
          "jcr:primaryType": "nt:unstructured",
          "jcr:mixinTypes": [
            "cq:LiveRelationship"
          ],
          "jcr:createdBy": "admin",
          "jcr:title": "Featured products",
          "jcr:lastModifiedBy": "admin",
          "cq:lastRolledout": "Mon Mar 14 2016 22:18:57 GMT+0100",
          "jcr:created": "Sun Mar 13 2016 23:10:36 GMT+0100",
          "type": "h2",
          "jcr:lastModified": "Mon Mar 14 2016 22:18:57 GMT+0100",
          "sling:resourceType": "weretail/components/content/title",
          "cq:lastRolledoutBy": "admin"
        },
        "product_grid": {
          "jcr:primaryType": "nt:unstructured",
          "jcr:mixinTypes": [
            "cq:LiveRelationship"
          ],
          "jcr:createdBy": "admin",
          "tagsMatch": "any",
          "orderBy": "jcr:title",
          "jcr:lastModifiedBy": "admin",
          "cq:lastRolledout": "Mon Mar 14 2016 22:18:58 GMT+0100",
          "feedEnabled": true,
          "pageMax": "0",
          "jcr:created": "Sun Mar 13 2016 22:39:31 GMT+0100",
          "limit": "6",
          "pages": [
            "/content/we-retail/us/en/products/men/shirts/eton-short-sleeve-shirt",
            "/content/we-retail/us/en/products/men/pants/trail-model-pants",
            "/content/we-retail/us/en/products/men/shorts/pipeline-board-shorts",
            "/content/we-retail/us/en/products/men/shirts/amsterdam-short-sleeve-travel-shirt",
            "/content/we-retail/us/en/products/men/shorts/buffalo-plaid-shorts",
            "/content/we-retail/us/en/products/men/coats/portland-hooded-jacket"
          ],
          "displayAs": "products",
          "jcr:lastModified": "Mon Mar 14 2016 22:18:57 GMT+0100",
          "sling:resourceType": "weretail/components/content/productgrid",
          "listFrom": "static",
          "cq:lastRolledoutBy": "admin"
        },
        "button": {
          "jcr:primaryType": "nt:unstructured",
          "jcr:mixinTypes": [
            "cq:LiveRelationship"
          ],
          "jcr:createdBy": "admin",
          "jcr:lastModifiedBy": "admin",
          "linkTo": "/content/we-retail/us/en/products/men",
          "cq:lastRolledout": "Mon Mar 14 2016 22:18:58 GMT+0100",
          "jcr:created": "Mon Mar 14 2016 21:41:39 GMT+0100",
          "label": "All men's products",
          "jcr:lastModified": "Mon Mar 14 2016 22:18:58 GMT+0100",
          "sling:resourceType": "weretail/components/content/button",
          "cq:lastRolledoutBy": "admin"
        },
        "teaser": {
          "jcr:primaryType": "nt:unstructured",
          "jcr:mixinTypes": [
            "cq:LiveRelationship"
          ],
          "jcr:createdBy": "admin",
          "jcr:title": "Push the limits",
          "fileReference": "/content/dam/we-retail/en/activities/biking/enduro-trail-jump.jpg",
          "actionsEnabled": true,
          "jcr:lastModifiedBy": "admin",
          "cq:lastRolledout": "Mon Mar 14 2016 22:18:58 GMT+0100",
          "jcr:created": "Sun Mar 13 2016 23:11:59 GMT+0100",
          "jcr:lastModified": "Mon Mar 14 2016 22:18:58 GMT+0100",
          "sling:resourceType": "weretail/components/content/teaser",
          "cq:lastRolledoutBy": "admin",
          "cq:responsive": {
            "jcr:primaryType": "nt:unstructured",
            "jcr:mixinTypes": [
              "cq:LiveRelationship"
            ],
            "cq:lastRolledout": "Mon Mar 14 2016 22:18:58 GMT+0100",
            "cq:lastRolledoutBy": "admin",
            "default": {
              "jcr:primaryType": "nt:unstructured",
              "jcr:mixinTypes": [
                "cq:LiveRelationship"
              ],
              "cq:lastRolledout": "Mon Mar 14 2016 22:18:58 GMT+0100",
              "width": "6",
              "cq:lastRolledoutBy": "admin"
            }
          },
          "actions": {
            "jcr:primaryType": "nt:unstructured",
            "item0": {
              "jcr:primaryType": "nt:unstructured",
              "link": "/content/we-retail/us/en/products/men/shirts",
              "text": "Our strongest clothes"
            }
          }
        },
        "teaser_980945351": {
          "jcr:primaryType": "nt:unstructured",
          "jcr:mixinTypes": [
            "cq:LiveRelationship"
          ],
          "jcr:createdBy": "admin",
          "jcr:title": "Always comfortable",
          "fileReference": "/content/dam/we-retail/en/activities/running/running-trail-man.jpg",
          "actionsEnabled": true,
          "jcr:lastModifiedBy": "admin",
          "cq:lastRolledout": "Mon Mar 14 2016 22:18:58 GMT+0100",
          "jcr:created": "Sun Mar 13 2016 23:12:01 GMT+0100",
          "jcr:lastModified": "Mon Mar 14 2016 22:18:58 GMT+0100",
          "sling:resourceType": "weretail/components/content/teaser",
          "cq:lastRolledoutBy": "admin",
          "cq:responsive": {
            "jcr:primaryType": "nt:unstructured",
            "jcr:mixinTypes": [
              "cq:LiveRelationship"
            ],
            "cq:lastRolledout": "Mon Mar 14 2016 22:18:58 GMT+0100",
            "cq:lastRolledoutBy": "admin",
            "default": {
              "jcr:primaryType": "nt:unstructured",
              "jcr:mixinTypes": [
                "cq:LiveRelationship"
              ],
              "cq:lastRolledout": "Mon Mar 14 2016 22:18:58 GMT+0100",
              "width": "6",
              "cq:lastRolledoutBy": "admin"
            }
          },
          "actions": {
            "jcr:primaryType": "nt:unstructured",
            "item0": {
              "jcr:primaryType": "nt:unstructured",
              "link": "/content/we-retail/us/en/products/men/coats",
              "text": "Our warmest jackets"
            }
          }
        },
        "title_1420670727": {
          "jcr:primaryType": "nt:unstructured",
          "jcr:mixinTypes": [
            "cq:LiveRelationship"
          ],
          "jcr:createdBy": "admin",
          "jcr:title": "Men's top stories",
          "jcr:lastModifiedBy": "admin",
          "cq:lastRolledout": "Mon Mar 14 2016 22:18:58 GMT+0100",
          "jcr:created": "Sun Mar 13 2016 23:22:37 GMT+0100",
          "type": "h2",
          "jcr:lastModified": "Mon Mar 14 2016 22:18:58 GMT+0100",
          "sling:resourceType": "weretail/components/content/title",
          "cq:lastRolledoutBy": "admin"
        },
        "list": {
          "jcr:primaryType": "nt:unstructured",
          "showModificationDate": "true",
          "jcr:createdBy": "admin",
          "tagsMatch": "any",
          "linkItems": "true",
          "orderBy": "modified",
          "jcr:lastModifiedBy": "admin",
          "jcr:created": "2018-02-06T17:59:00.584+01:00",
          "parentPage": "/content/we-retail/us/en/experience",
          "sortOrder": "asc",
          "showDescription": "false",
          "jcr:lastModified": "2018-02-06T18:41:21.863+01:00",
          "itemType": "article",
          "sling:resourceType": "weretail/components/content/list",
          "childDepth": "1",
          "listFrom": "children",
          "cq:styleIds": [
            "1508797744638",
            "1508800123708"
          ]
        }
      }
    }
  }
}

That’s all! I hope you like this kind of approach for testing servlet! Check here other Junit Test examples

Cheers! 🥂

Leave a comment