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! 🍻