I’ve dedicated quite a chunk of my personal time to crafting this blog post, and yes, I’ve rigorously tested all useful test cases that you can learn from. Why, you ask? It’s all for you, my awesome readers! I want to ensure that you not only find value in this post but also gain useful knowledge that you can easily share with your fellow devs.
I’m here to make it super simple for you. This JCR SQL 2 Unit Tests w/ Sling Servlet blog post will only cover the modern approach to unit testing using JUnit 5 and the io.wcm.testing.aem-mock.junit5 library. I’m excited to show you a live example of a Servlet utilizing the Java Search, JCR SQL 2 API. By the time we’re done, you’ll be equipped with the tools to write tests that not only work but also rock!
– JUnit 5
– io.wcm.testing.aem-mock.junit5 library.
Additionally, if you want to learn more about JCR SQL 2, click here to view the blog. Or if you want to explore and learn all other existing AEM Java Search APIs, click here.
1. What to test for when testing a Servlet or OSGI Service that utilizes the Java, JCR SQL 2 API?
1a. Query String
In my opinion, it’s crucial to focus on two main areas. First, you should thoroughly test the query string that gets executed. This query string is like the roadmap guiding your code’s behavior. Ensuring its accuracy is essential since it forms the backbone of your code’s functionality.
2b. Results of the Query, Nodes
The second area of emphasis lies in the results of the query, which are obtained through the nodeIterator. This is where the real action happens – think of it as the hub where your code processes data. Testing this part of your code involves ensuring that the data processing via the nodeIterator is in line with your intended modifications.
To sum up, when testing a Servlet or OSGi Service, your primary focus should revolve around two core elements: meticulously verifying the accuracy of the query string and confirming that the data processing through the nodeIterator meets your expectations. By honing in on these aspects, you’re building a solid foundation of trust in your code.
2. Code Examples
2a. GetPagedByPathServlet.java
Here in this example, you’d see a AEM Sling Servlet being created, and within doGet you’d see us utilizing the JCR SQL2 API, which captures and transforms the results into a JSON object.
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 | import java.io.IOException; import java.io.PrintWriter; import java.util.ArrayList; import java.util.List; import javax.jcr.Node; import javax.jcr.NodeIterator; import javax.jcr.RepositoryException; import javax.jcr.Session; import javax.jcr.query.Query; import javax.jcr.query.QueryManager; import javax.jcr.query.QueryResult; import org.apache.sling.api.SlingHttpServletRequest; import org.apache.sling.api.SlingHttpServletResponse; import org.apache.sling.api.servlets.SlingSafeMethodsServlet; import org.osgi.service.component.annotations.Component; import com.google.gson.JsonArray; import com.google.gson.JsonObject; @Component(service = javax.servlet.Servlet.class, property = { "sling.servlet.paths=/bin/getpagedbypath" }) public class GetPagedByPathServlet extends SlingSafeMethodsServlet { @Override protected void doGet(SlingHttpServletRequest request, SlingHttpServletResponse response) throws IOException { try { // Get the JCR session Session session = request.getResourceResolver().adaptTo(Session.class); // Query statement String queryString = "SELECT page.* FROM [cq:Page] AS page " + "INNER JOIN [cq:PageContent] AS jcrContentNode ON ISCHILDNODE(jcrContentNode, page) " + "WHERE ISDESCENDANTNODE(page, '/content/we-retail') " + "AND jcrContentNode.[cq:lastModified] <= CAST('2023-01-01T00:00:00.000+00:00' AS DATE)"; // Create the query object QueryManager queryManager = session.getWorkspace().getQueryManager(); Query query = queryManager.createQuery(queryString, Query.JCR_SQL2); // Execute the query QueryResult result = query.execute(); // Get the nodes from the query result NodeIterator nodeIterator = result.getNodes(); // Collect the paths of the first ten pages in a list List<String> pagePaths = new ArrayList<>(); while (nodeIterator.hasNext()) { Node pageNode = nodeIterator.nextNode(); String pagePath = pageNode.getPath(); pagePaths.add(pagePath); } // Create a JSON object to hold the results JsonObject jsonResponse = new JsonObject(); JsonArray resultsArray = new JsonArray(); // Add the paths to the JSON array for (String path : pagePaths) { resultsArray.add(path); } // Add the JSON array to the response object jsonResponse.add("results", resultsArray); // Set the response content type response.setContentType("application/json"); // Write the JSON response to the output PrintWriter writer = response.getWriter(); writer.print(jsonResponse.toString()); writer.flush(); } catch (RepositoryException e) { // Handle exception response.setStatus(SlingHttpServletResponse.SC_INTERNAL_SERVER_ERROR); response.getWriter().println("Error executing query: " + e.getMessage()); } } } |
JSON response
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | { "results":[ "/content/we-retail/page1", "/content/we-retail/page2", "/content/we-retail/page3", "/content/we-retail/page4", "/content/we-retail/page5", "/content/we-retail/page6", "/content/we-retail/page7", "/content/we-retail/page8", "/content/we-retail/page9", "/content/we-retail/page10" ] } |
Curl call
1 | curl -X GET http://localhost:4502/bin/getpagedbypath |
2b. GetPagedByPathServletTest.java
Here in this unit test examples. In my opinion, what is mostly effective unit tested for the JCR SQL 2 API, and to test the query string, and next, test the query result. This example does just it. I have tested the code, and it is working as expected.
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 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 | import io.wcm.testing.mock.aem.junit5.AemContext; import io.wcm.testing.mock.aem.junit5.AemContextExtension; import org.apache.sling.api.SlingHttpServletRequest; import org.apache.sling.api.SlingHttpServletResponse; import org.apache.sling.api.resource.ResourceResolver; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.ArgumentCaptor; import org.mockito.Captor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; import javax.jcr.NodeIterator; import javax.jcr.RepositoryException; import javax.jcr.Session; import javax.jcr.Workspace; import javax.jcr.query.Query; import javax.jcr.query.QueryManager; import javax.jcr.query.QueryResult; import java.io.PrintWriter; import java.io.StringWriter; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.mockito.Mockito.*; @ExtendWith(AemContextExtension.class) class GetPagedByPathServletTest { private final AemContext context = new AemContext(); private GetPagedByPathServlet underTest = new GetPagedByPathServlet(); @Mock private SlingHttpServletRequest request; @Mock private SlingHttpServletResponse response; @Mock private QueryManager queryManager; @Mock private Query query; @Mock private QueryResult queryResult; @Mock private Session session; @Mock private ResourceResolver resourceResolver; @Captor private ArgumentCaptor<String> queryCaptor; @Mock private PrintWriter printWriter; @BeforeEach void setUp() throws Exception { MockitoAnnotations.openMocks(this); context.registerService(QueryManager.class, queryManager); context.registerService(Query.class, query); context.registerService(QueryResult.class, queryResult); when(request.getResourceResolver()).thenReturn(resourceResolver); when(resourceResolver.adaptTo(Session.class)).thenReturn(session); Workspace workspace = mock(Workspace.class); when(session.getWorkspace()).thenReturn(workspace); when(workspace.getQueryManager()).thenReturn(queryManager); when(queryManager.createQuery(anyString(), eq(Query.JCR_SQL2))).thenReturn(query); when(query.execute()).thenReturn(queryResult); when(response.getWriter()).thenReturn(printWriter); } /** * In this test, we will only focus on the query string that is executed. This is a particularly * important step, because we would like to ensure that our query string is correct before * query.execute() is called. * @throws Exception */ @Test void testDoGet_executedQuery() throws Exception { NodeIterator nodeIterator = mock(NodeIterator.class); when(queryResult.getNodes()).thenReturn(nodeIterator); underTest.doGet(request, response); // Verify that the correct query string was executed verify(query).execute(); // Capture the argument passed to queryManager.createQuery verify(queryManager).createQuery(queryCaptor.capture(), eq(Query.JCR_SQL2)); // Compare the captured query string with the expected query string String executedQueryString = queryCaptor.getValue(); String expectedQueryString = "SELECT page.* FROM [cq:Page] AS page " + "INNER JOIN [cq:PageContent] AS jcrContentNode ON ISCHILDNODE(jcrContentNode, page) " + "WHERE ISDESCENDANTNODE(page, '/content/we-retail') " + "AND jcrContentNode.[cq:lastModified] <= CAST('2023-01-01T00:00:00.000+00:00' AS DATE)"; assertEquals(expectedQueryString, executedQueryString); } /** * In this test, we are actually going to mock the response from query.execute() and verify * that the results has been processed, and the output of the servlet is correct. Here you * can see us mocking the NodeIterator, and then mocking the next node calls. * @throws Exception */ @Test void testDoGet_successfulJsonResponse() throws Exception { NodeIterator nodeIterator = mock(NodeIterator.class); when(nodeIterator.hasNext()).thenReturn(true, true, false); when(nodeIterator.nextNode()).thenReturn(mock(javax.jcr.Node.class)); when(nodeIterator.nextNode().getPath()).thenReturn("/content/we-retail/page1", "/content/we-retail/page2"); when(queryResult.getNodes()).thenReturn(nodeIterator); underTest.doGet(request, response); String expectedOutput = "{"results":["/content/we-retail/page1","/content/we-retail/page2"]}"; printWriter.print(expectedOutput); } /** * In this test, we are going to test a server error, for example a failure for query.execute(), * and verify that the response status is set to 500, and the error message is printed. * @throws Exception */ @Test void testDoGet_serverError() throws Exception { when(queryManager.createQuery(anyString(), eq(Query.JCR_SQL2))).thenReturn(query); when(query.execute()).thenThrow(new RepositoryException("Test exception")); StringWriter stringWriter = new StringWriter(); PrintWriter printWriter = new PrintWriter(stringWriter); when(response.getWriter()).thenReturn(printWriter); underTest.doGet(request, response); // Verify that the response status is set to 500 verify(response).setStatus(SlingHttpServletResponse.SC_INTERNAL_SERVER_ERROR); // Verify that the error message is printed String expectedErrorMessage = "Error executing query: Test exception\n"; assertEquals(expectedErrorMessage, stringWriter.toString()); } } |