December 31, 2020
Estimated Post Reading Time ~

Unit Testing Hands on - For Sling Servlet

This post is about creating a Unit Test class for Sling Servlet, another commonly used Java class as part of an AEM application.
In Sling servlets, we have

SlingSafeMethodsServlet - read only servlet supporting GET (doGet)
SlingAllMethodsServlet - Supports POST, PUT and DELETE (doPost/doPut/doDelete)


In either case, we have request and response objects using which desired code logic is written in Servlet.
For Unit testing, we have Mock sling request and sling response from Sling Mocks (org.apache.sling.testing.mock.sling.servlet.MockSlingHttpServletRequest and org.apache.sling.testing.mock.sling.servlet.MockSlingHttpServletResponse respectively)

Writing unit test for Servlet involves the following
  • Instantiate Servlet to be tested.
  • Get mock request and response from AemContext
  • Prepare the mock request object (per the code logic before calling doGet/doPost)
  • Call doGet/doPost method of Servlet to be tested
  • Assert the Servlet response (per the code logic)
Instantiate Servlet to be tested.
Considering the Servlet to be tested is UnitTestServlet.java,
    UnitTestServlet unitTestServletObj = new UnitTestServlet();

Get request and response from AemContext:
From the AemContext object, we can create a request and response which is MockSlingHttpServletRequest and MockSlingHttpServletResponse respectively as explained above.
    MockSlingHttpServletRequest mockSlingRequest = aemContext.request();
    MockSlingHttpServletResponse mockSlingResponse = aemContext.response();


Prepare a mock request object:
  • Consider a scenario where we have a Servlet to retrieve values from the request and perform logic based on that.
  • Lets say, parameter named "siteName" is retrieved from request via request.getParameter("siteName") where siteName is being passed from HTML form or ajax request/any related.
  • Now for unit testing, we need to explicitly set the parameter to the mock request. So when the Servlet method is tested and when the above line executes, we will have a value for the "siteName".
Prepare Map object and set it to mock sling request as below

private Map<String, Object> parameterMap = new HashMap<String, Object>();
parameterMap.put("siteName", "aemlearnings");
mockSlingRequest.setParameterMap(parameterMap);


Similarly, we can gain access to other methods available in MockSlingRequest to prepare mock sling request (mockSlingRequest) per the code logic for testing. (mockSlingRequest.setResource(), mockSlingRequest.addHeader and so on)

Call doGet/doPost method of Servlet to be tested:

Once when the mock sling request object is prepared, we can call the respective method using the servlet object that we instantiated per the first step.
    unitTestServletObj.doGet(mockSlingRequest, mockSlingResponse);

When this line gets executed, code logic in the actual doGet method will be executed. If there is anything that is missed to set up in the Test class file, then this line might throw "NullPointerException"

Example:
Let say code logic in doGet makes use of an OSGI service + if that OSGI service is not registered in Test class file/ in a mock sense, if we don't define any dummy implementation for any of the lines of code, then the respective object will be null and hence exception in the above line of execution (or if an exception is handled, it will ultimately result in an error related to Assertion)

Assert the Servlet response:
Based on the code logic related to the response object, we can assert accordingly.
Example to assert the status code/check the content type set/anything written to the response object.

/* Checking content Type */
assertEquals("application/json", mockSlingResponse.getContentType());
/* Checking response status code */
assertEquals(200, mockSlingResponse.getStatus());
/* Checking output of servlet */
assertTrue(mockSlingResponse.getOutputAsString().contains("Response String from Servlet")); [where the actual line of code for this is something like - resp.getWriter().write("Response String from Servlet=" + responseStr);]


Code on GitHub:
Full Sample Servlet and Unit Test class for the same is available in GitHub
As such it doesn't refine to a specific functionality but to illustrate the below
  • Reference an OSGi Service
  • Call a dummy REST API (available from POSTMAN) and writes the API response to the Servlet response object.
Reference an OSGI service:
We have an option to register/inject OSGi service with the help of AemContext.
Considering an OSGI service (with config) is referenced in a Servlet, then in Test class file, we need to register that service using AemContext - We have few related methods from AemContext for the same. Based on the nature of the OSGi service referenced, we can use the respective method accordingly.

A full list of methods related to this is available under the "Methods from OSGIContextImpl" section in API Doc

Prepare the config properties using a Map object
Use registerInjectActivateService method from AemContext

    private SampleOSGIService mockSampleOSGIService; // OSGI service
    private Map<String, String> configProps = new HashMap<String, String>();
    configProps.put("apiEndpoint", "https://postman-echo.com");
    configProps.put("apiKey", "XYZ");
    configProps.put("siteName", "aemlearnings");
    mockSampleOSGIService = aemContext.registerInjectActivateService(new      
    SampleOSGIServiceImpl(), configProps);

Other use cases part of a Servlet:
  • Logic related to accessing a resource from request/performing any iteration/related actions on AEM APIs like Page or Asset or Tag or any related/Query Builder based logic to get the result based on some condition via predicates Map and so on.
  • We can mock the respective API if it is not directly available from AemContext -> provide dummy implementation and hence assert accordingly.


By aem4beginner

No comments:

Post a Comment

If you have any doubts or questions, please let us know.