JUnit Test in AEM 6.5 – Query Builder

How to test a Query Builder with AEMContext in AEM

Goal

Test Query Builder using AEMContext. We will extend the Generic Abstract Class created here.
In this article we are going to test the SearchResultServlet available here.

Procedure

We will focus here on how to test a class that uses Query Builder. If you are interested to understand in general the approach used to test a servlet, please read the article here. You will find also the “AbstractServletTest” class I’m extending here.

Let’s proceed and create our JUnit test:

package com.adobe.training.core.servlets;


import com.day.cq.search.PredicateGroup;
import com.day.cq.search.result.Hit;
import com.day.cq.wcm.api.NameConstants;
import io.wcm.testing.mock.aem.junit.AemContext;
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.apache.sling.testing.mock.sling.ResourceResolverType;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;

import javax.jcr.RepositoryException;
import javax.jcr.Session;
import java.io.IOException;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

/**
 * SearchResultServletTest Class.
 *
 * @author riccardo.teruzzi
 */
@RunWith(MockitoJUnitRunner.class)
public class SearchResultServletTest extends AbstractServletTest {


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

    @Mock
    private Resource resource1;

    @Mock
    private Resource resource2;

    @Mock
    private Resource resource3;

    @InjectMocks
    private SearchResultServlet searchResultServlet;

    private SearchResultServletRequest searchResultServletRequest;

    private Iterator<Resource> mockIterator = mock(Iterator.class);


    @Before
    public void setUp() throws NoSuchFieldException {
        aemContext = new AemContext(ResourceResolverType.JCR_MOCK);
        aemContext.requestPathInfo().setSelectorString("search");
        super.setUp();
        Map<String, Object> pm = new HashMap<>();
        pm.put("fulltext", "word");
        request.setParameterMap(pm);
        setCurrentResource(CURRENT_PAGE_PATH);
        searchResultServletRequest = new SearchResultServletRequest(request);

    }

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


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

    public void configureQueryBuilder() {

        final List<Hit> results = new ArrayList<>();
        Hit pageResult1 = mock(Hit.class);
        Hit pageResult2 = mock(Hit.class);
        Hit pageResult3 = mock(Hit.class);

        try {
            when(pageResult1.getPath()).thenReturn("/content/we-retail/us");
            resource1 = resourceResolver.getResource("/content/we-retail/us");
            when(pageResult1.getResource()).thenReturn(resource1);

            when(pageResult2.getPath()).thenReturn("/content/we-retail/it");
            resource2 = resourceResolver.getResource("/content/we-retail/it");
            when(pageResult2.getResource()).thenReturn(resource2);

            when(pageResult3.getPath()).thenReturn("/content/we-retail/fr");
            resource3 = resourceResolver.getResource("/content/we-retail/fr");
            when(pageResult3.getResource()).thenReturn(resource3);
        } catch (RepositoryException e) {
            LOG.error("Failed to retrieve path", e);
        }
        results.add(pageResult3);
        results.add(pageResult1);
        results.add(pageResult2);

        Map<String, String> predicatesMap = new HashMap<>();

        predicatesMap.put("fulltext", "word");
        predicatesMap.put("path", CURRENT_PAGE_PATH);
        predicatesMap.put("type", NameConstants.NT_PAGE);
        PredicateGroup predicateGroup = PredicateGroup.create(predicatesMap);
        when(queryBuilder.createQuery(predicateGroup, request.getResourceResolver().adaptTo(Session.class))).thenReturn(query);

        //OR MORE GENERIC

        //when(queryBuilder.createQuery(any(PredicateGroup.class), any(Session.class))).thenReturn(query);

        when(query.getResult()).thenReturn(searchResults);
        when(searchResults.getHits()).thenReturn(results);
        when(searchResults.getTotalMatches()).thenReturn(3L);
    }

    private class SearchResultServletRequest extends SlingHttpServletRequestWrapper {

        /**
         * Instantiates a new my request.
         *
         * @param wrappedRequest the wrapped request
         */
        public SearchResultServletRequest(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;
        }

    }
}

In the setUp method we initialize the request to proper simulate the search. Then, we will make use intensively of the when/then methodology in the configureQueryBuilder method. Finally, every mock resource will be correctly retrieved from our repository, created with the following loadedJSON:

{
  "jcr:primaryType": "cq:Page",
  "jcr:createdBy": "admin",
  "jcr:created": "Sat Oct 24 2020 12:39:58 GMT+0200",
  "jcr:content": {
    "jcr:primaryType": "cq:PageContent",
    "jcr:createdBy": "admin",
    "jcr:title": "We.Retail",
    "cq:redirectTarget": "/content/we-retail/us/en",
    "jcr:created": "Sat Oct 24 2020 12:39:58 GMT+0200",
    "cq:lastModified": "Tue Feb 09 2016 00:05:48 GMT+0100",
    "cq:cloudserviceconfigs": [

    ],
    "cq:deviceGroups": [
      "mobile/groups/responsive"
    ],
    "cq:conf": "/conf/we-retail",
    "sling:resourceType": "weretail/components/structure/page",
    "cq:allowedTemplates": [
      "/conf/we-retail/settings/wcm/templates/.*"
    ],
    "cq:lastModifiedBy": "admin",
    "image": {
      "jcr:primaryType": "nt:unstructured",
      "imageRotate": "0",
      "file": {
        "jcr:primaryType": "nt:file",
        "jcr:createdBy": "admin",
        "jcr:created": "Sat Oct 24 2020 12:39:58 GMT+0200"
      }
    }
  },
  "us": {
    "jcr:primaryType": "cq:Page",
    "jcr:createdBy": "admin",
    "jcr:created": "Sat Oct 24 2020 12:39:58 GMT+0200",
    "en": {
      "jcr:primaryType": "cq:Page",
      "jcr:createdBy": "admin",
      "jcr:created": "Sat Oct 24 2020 12:39:58 GMT+0200",
      "jcr:content": {
        "jcr:primaryType": "cq:PageContent",
        "jcr:mixinTypes": [
          "cq:LiveRelationship",
          "cq:LiveSync"
        ],
        "jcr:createdBy": "admin",
        "jcr:title": "English",
        "cq:commerceProvider": "we-retail",
        "cq:template": "/conf/we-retail/settings/wcm/templates/hero-page",
        "jcr:language": "en_US",
        "cq:lastRolledout": "Tue Aug 21 2018 16:48:17 GMT+0300",
        "cq:checkoutPage": "/content/we-retail/us/en/user/cart",
        "cq:contextHubSegmentsPath": "/conf/we-retail/settings/wcm/segments",
        "jcr:created": "Sat Oct 24 2020 12:39:58 GMT+0200",
        "cq:lastModified": "Tue Aug 21 2018 16:48:17 GMT+0300",
        "cq:cartPage": "/content/we-retail/us/en/user/cart",
        "sling:resourceType": "weretail/components/structure/page",
        "cq:contextHubPath": "/libs/settings/cloudsettings/legacy/contexthub",
        "cq:lastRolledoutBy": "admin",
        "navRoot": true,
        "cq:lastModifiedBy": "admin"
      },
      "experience": {
        "jcr:primaryType": "cq:Page",
        "jcr:createdBy": "admin",
        "jcr:created": "Sat Oct 24 2020 12:39:59 GMT+0200"
      },
      "men": {
        "jcr:primaryType": "cq:Page",
        "jcr:createdBy": "admin",
        "jcr:created": "Sat Oct 24 2020 12:40:02 GMT+0200"
      },
      "women": {
        "jcr:primaryType": "cq:Page",
        "jcr:createdBy": "admin",
        "jcr:created": "Sat Oct 24 2020 12:40:02 GMT+0200"
      },
      "equipment": {
        "jcr:primaryType": "cq:Page",
        "jcr:createdBy": "admin",
        "jcr:created": "Sat Oct 24 2020 12:40:02 GMT+0200"
      },
      "about-us": {
        "jcr:primaryType": "cq:Page",
        "jcr:createdBy": "admin",
        "jcr:created": "Sat Oct 24 2020 12:40:02 GMT+0200"
      },
      "products": {
        "jcr:primaryType": "cq:Page",
        "jcr:createdBy": "admin",
        "jcr:created": "Sat Oct 24 2020 12:39:59 GMT+0200"
      },
      "activitystreams": {
        "jcr:primaryType": "nt:unstructured"
      },
      "user": {
        "jcr:primaryType": "cq:Page",
        "jcr:createdBy": "admin",
        "jcr:created": "Sat Oct 24 2020 12:40:02 GMT+0200"
      },
      "community": {
        "jcr:primaryType": "cq:Page",
        "jcr:mixinTypes": [
          "rep:AccessControllable"
        ],
        "jcr:createdBy": "admin",
        "jcr:created": "Sat Oct 24 2020 12:40:21 GMT+0200"
      }
    },
    "es": {
      "jcr:primaryType": "cq:Page",
      "jcr:createdBy": "admin",
      "jcr:created": "Sat Oct 24 2020 12:39:58 GMT+0200",
      "jcr:content": {
        "jcr:primaryType": "cq:PageContent",
        "jcr:mixinTypes": [
          "cq:LiveRelationship",
          "cq:LiveSync"
        ],
        "jcr:createdBy": "admin",
        "jcr:title": "Español",
        "cq:template": "/conf/we-retail/settings/wcm/templates/hero-page",
        "jcr:language": "es_US",
        "cq:lastRolledout": "Tue Aug 21 2018 17:00:10 GMT+0300",
        "cq:contextHubSegmentsPath": "/conf/we-retail/settings/wcm/segments",
        "jcr:created": "Sat Oct 24 2020 12:39:58 GMT+0200",
        "cq:lastModified": "Tue Aug 21 2018 17:00:11 GMT+0300",
        "sling:resourceType": "weretail/components/structure/page",
        "cq:contextHubPath": "/libs/settings/cloudsettings/legacy/contexthub",
        "cq:lastRolledoutBy": "admin",
        "navRoot": true,
        "cq:lastModifiedBy": "admin"
      }
    },
    "jcr:content": {
      "jcr:primaryType": "cq:PageContent",
      "jcr:createdBy": "admin",
      "jcr:title": "United States",
      "cq:template": "/conf/we-retail/settings/wcm/templates/redirect-page",
      "cq:redirectTarget": "/content/we-retail/us/en",
      "jcr:created": "Sat Oct 24 2020 12:39:58 GMT+0200",
      "cq:lastModified": "Mon Mar 07 2016 17:32:10 GMT+0100",
      "sling:resourceType": "weretail/components/structure/page",
      "cq:lastModifiedBy": "admin"
    }
  },
  "fr": {
    "jcr:primaryType": "cq:Page",
    "jcr:createdBy": "admin",
    "jcr:created": "Sat Oct 24 2020 12:40:11 GMT+0200",
    "jcr:content": {
      "jcr:primaryType": "cq:PageContent",
      "jcr:createdBy": "admin",
      "jcr:title": "France",
      "cq:template": "/conf/we-retail/settings/wcm/templates/redirect-page",
      "jcr:language": "fr_FR",
      "cq:redirectTarget": "/content/we-retail/fr/fr",
      "jcr:created": "Sat Oct 24 2020 12:40:11 GMT+0200",
      "cq:lastModified": "Fri Sep 23 2016 16:23:19 GMT-0700",
      "sling:resourceType": "weretail/components/structure/page",
      "cq:lastModifiedBy": "admin"
    },
    "fr": {
      "jcr:primaryType": "cq:Page",
      "jcr:createdBy": "admin",
      "jcr:created": "Sat Oct 24 2020 12:40:11 GMT+0200",
      "jcr:content": {
        "jcr:primaryType": "cq:PageContent",
        "jcr:mixinTypes": [
          "cq:LiveRelationship",
          "cq:LiveSync"
        ],
        "jcr:createdBy": "admin",
        "jcr:title": "Français",
        "cq:template": "/conf/we-retail/settings/wcm/templates/hero-page",
        "jcr:language": "fr_FR",
        "cq:lastRolledout": "Tue Aug 21 2018 17:11:26 GMT+0300",
        "cq:contextHubSegmentsPath": "/conf/we-retail/settings/wcm/segments",
        "jcr:created": "Sat Oct 24 2020 12:40:11 GMT+0200",
        "cq:lastModified": "Tue Aug 21 2018 17:11:28 GMT+0300",
        "sling:resourceType": "weretail/components/structure/page",
        "cq:contextHubPath": "/libs/settings/cloudsettings/legacy/contexthub",
        "cq:lastRolledoutBy": "admin",
        "navRoot": true,
        "cq:lastModifiedBy": "admin"
      }
    }
  },
  "it": {
    "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:createdBy": "admin",
      "jcr:title": "Italy",
      "cq:template": "/conf/we-retail/settings/wcm/templates/redirect-page",
      "jcr:language": "it_IT",
      "cq:redirectTarget": "/content/we-retail/it/it",
      "jcr:created": "Sat Oct 24 2020 12:40:02 GMT+0200",
      "cq:lastModified": "Fri Sep 23 2016 16:23:57 GMT-0700",
      "sling:resourceType": "weretail/components/structure/page",
      "cq:lastModifiedBy": "admin"
    },
    "it": {
      "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:LiveSync"
        ],
        "jcr:createdBy": "admin",
        "jcr:title": "Italiano",
        "cq:template": "/conf/we-retail/settings/wcm/templates/hero-page",
        "jcr:language": "it_IT",
        "cq:lastRolledout": "Tue Aug 21 2018 17:11:03 GMT+0300",
        "cq:contextHubSegmentsPath": "/conf/we-retail/settings/wcm/segments",
        "jcr:created": "Sat Oct 24 2020 12:40:02 GMT+0200",
        "cq:lastModified": "Tue Aug 21 2018 17:11:05 GMT+0300",
        "sling:resourceType": "weretail/components/structure/page",
        "cq:contextHubPath": "/libs/settings/cloudsettings/legacy/contexthub",
        "cq:lastRolledoutBy": "admin",
        "navRoot": true,
        "cq:lastModifiedBy": "admin"
      }
    }
  },
  "resources": {
    "jcr:primaryType": "nt:unstructured",
    "jcr:mixinTypes": [
      "vlt:FullCoverage"
    ],
    "states": {
      "jcr:primaryType": "nt:unstructured",
      "us": {
        "jcr:primaryType": "nt:unstructured"
      }
    }
  }
}

In this case, the expected JSON will be the following:

{"totalMatches":3,"results":[{"title":"France","url":"/we-retail/fr.html"},{"title":"United States","url":"/we-retail/us.html"},{"title":"Italy","url":"/we-retail/it.html"}]}

That’s all!! The code should be readable, therefore I didn’t add other explanation, but if you need something drop a comment! For other Junit Test examples, check them out here.

Cheers! 🍻

Leave a comment