Expand my Community achievements bar.

Dive into Adobe Summit 2024! Explore curated list of AEM sessions & labs, register, connect with experts, ask questions, engage, and share insights. Don't miss the excitement.

How to Unit testing for doPost Servlet which contains HTTP Request

Avatar

Level 2

Hi Guys, Can anyone help me in creating Junit test cases for my servlet. I am new to AEM development and not aware of Junit thing. I create below servlet which will retrive rates from an external API in JSON format.

so plz help for unit testing. i need unit testing script for this code, this my code.

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.TimeZone;
import java.util.UUID;
import javax.crypto.Mac;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import javax.jcr.RepositoryException;
import javax.servlet.Servlet;
import javax.servlet.ServletException;
import com.google.gson.JsonArray;
import com.google.gson.JsonParser;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.lang.StringUtils;
import org.apache.http.HttpEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.api.SlingHttpServletResponse;
import org.apache.sling.api.servlets.HttpConstants;
import org.apache.sling.api.servlets.SlingAllMethodsServlet;
import com.google.gson.JsonObject;
import org.osgi.framework.Constants;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.metatype.annotations.AttributeDefinition;
import org.osgi.service.metatype.annotations.AttributeType;
import org.osgi.service.metatype.annotations.Designate;
import org.osgi.service.metatype.annotations.ObjectClassDefinition;
import org.apache.sling.commons.osgi.PropertiesUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@component(service = Servlet.class, property = {Constants.SERVICE_DESCRIPTION + "=Rates Servlet",
"sling.servlet.methods=" + HttpConstants.METHOD_POST, "sling.servlet.paths=" + "/services/rates/"
})
@Designate(ocd = RatesServlet.ServiceConfig.class)
public class RatesServlet extends SlingAllMethodsServlet {
protected final Logger log = LoggerFactory.getLogger(RatesServlet.class);
private String apiURL;
private String tokenApiURL;
private String secret;
private String appID;
private static final UUID uuid = UUID.randomUUID();
private static final String correlationID=uuid.toString();
private static final String scope = "/**/communications::POST";
private String token;
@Override
protected void doPost(final SlingHttpServletRequest req, final SlingHttpServletResponse resp)
throws ServletException, IOException {
try {
generateToken(req);
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (InvalidKeyException e) {
e.printStackTrace();
}
String market = req.getParameter("market");
TimeZone tz = TimeZone.getTimeZone("UTC");
DateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"); // Quoted "Z" to indicate UTC, no timezone offset
df.setTimeZone(tz);
String nowAsISO = df.format(new Date());
CloseableHttpClient client = HttpClients.createDefault();
HttpPost method = new HttpPost(this.apiURL);
JsonObject json = new JsonObject();
json.addProperty("market", market);
json.addProperty("rateVarianceTimestamp", nowAsISO);
StringEntity entity = new StringEntity(json.toString(), ContentType.APPLICATION_JSON);
method.setEntity(entity);
String authorizationHeader="claims app_authorization="+String.valueOf(this.token);
method.setHeader("Content-Type", "application/json");
method.setHeader("app-id", this.appID);
method.setHeader("Authorization", authorizationHeader);
try {
CloseableHttpResponse response = client.execute(method);
System.out.println(response);
String ratesVarianceResponse = EntityUtils.toString(response.getEntity());
resp.setContentType("application/json");
resp.getWriter().write(ratesVarianceResponse);
} catch (Exception e) {
log.error("Exception in servlet while executing :", e.getMessage());
e.printStackTrace();
} finally {
method.releaseConnection();
}
}
protected void generateToken(SlingHttpServletRequest req) throws NoSuchAlgorithmException, InvalidKeyException, UnsupportedEncodingException {
Mac mac = Mac.getInstance("HmacSHA256");
byte[] hmacKeyBytes = Base64.decodeBase64(this.secretOneData.getBytes());
SecretKey secretKey = new SecretKeySpec(hmacKeyBytes, mac.getAlgorithm());
mac.init(secretKey);
long timestamp = System.currentTimeMillis();
String input = String.format("%s-%s-%s", this.clientIdOneData, "2", timestamp);
byte[] rawHmac = mac.doFinal(input.getBytes());
//signature
String signature = Base64.encodeBase64URLSafeString(rawHmac);
CloseableHttpClient client = HttpClients.createDefault();
HttpPost httpPost = new HttpPost(this.tokenApiURL);
String json = "{\n"
+ " \"scope\": [\n"
+ " \"" + scope + "\"\n"
+ " ]\n"
+ "}";
StringEntity entity = new StringEntity(json);
httpPost.setEntity(entity);
httpPost.setHeader("X-Auth-AppID", this.appID);
httpPost.setHeader("X-Auth-Version", "2");
httpPost.setHeader("X-Auth-Signature", signature);
httpPost.setHeader("Content-Type", "application/json");
httpPost.setHeader("X-Auth-Timestamp", String.valueOf(timestamp));
try {
CloseableHttpResponse response = client.execute(httpPost);
HttpEntity httpentity = response.getEntity();
String signatureToken = EntityUtils.toString(httpentity);
JsonParser parser = new JsonParser();
Object resultObject = parser.parse(signatureToken);
Object keyvalue = null;
if (resultObject instanceof JsonArray) {
JsonArray array=(JsonArray)resultObject;
for (Object object : array) {
JsonObject obj =(JsonObject)object;
keyvalue = obj.get("authorization_token").getAsString();
}
this.token=String.valueOf(keyvalue);
}else if (resultObject instanceof JsonObject) {
JsonObject obj =(JsonObject)resultObject;
keyvalue = obj.get("authorization_token").getAsString();
this.token=String.valueOf(keyvalue);
}
} catch (Exception e) {
log.error("Exception in servlet while executing :", e.getMessage());
e.printStackTrace();
} finally {
httpPost.releaseConnection();
}
}
@activate
protected void activate(ServiceConfig serviceConfig) throws RepositoryException {
apiURL = PropertiesUtil.toString(serviceConfig.apiURL(), StringUtils.EMPTY);
tokenApiURL = PropertiesUtil.toString(serviceConfig.tokenApiURL(), StringUtils.EMPTY);
secretData = PropertiesUtil.toString(serviceConfig.secretOneData(), StringUtils.EMPTY);
appID = PropertiesUtil.toString(serviceConfig.clientIdOneData(), StringUtils.EMPTY);
}
@ObjectClassDefinition(name = "Rates Servlet", description = "This service makes an API call and retrieves the rates values")
public @interface ServiceConfig {
@AttributeDefinition(name = "API Url", description = "Please provide the API URL", type= AttributeType.STRING)
String apiURL();
@AttributeDefinition(name = "Token API URL", description = "Please provide the A2A Token generation API URL", type= AttributeType.STRING)
String tokenApiURL();
@AttributeDefinition(name = "Secret Data", description = "Please Provide domain specific secret of Consuming API", type= AttributeType.STRING)
String secretData();
@AttributeDefinition(name = "App ID", description = "Please provide application ID", type= AttributeType.STRING)
String appID();
}
}

6 Replies

Avatar

Level 2

Hi @Mayank_Tiwari , Thanks for sharing these, found some similar requirement in https://myaemlearnings.blogspot.com/2020/07/unit-testing-hands-on-for-sling-servlet.html, but here OSGI configuration variables are predefined in Test class, in my case i do not want to define them either in my main servlet or test class. Could you help, how we can do that? 

Avatar

Community Advisor

@siva_k222,

OSGi config values created as Map object is the way we mock OSGi config in the Test class. The same is then used while registering the service (with its config) to the AemContext. - https://wcm.io/testing/aem-mock/junit5/apidocs/org/apache/sling/testing/mock/osgi/context/OsgiContex...

 

Outside this, if you would like to test HttpClient, you can refer - https://blogs.perficient.com/2019/01/23/how-to-test-apache-httpclient-in-the-context-of-aem/

 

Avatar

Level 2

Hi @Vijayalakshmi_S ,

You mean to say, we must use OSGi config values in Test class? is there any way we can get the values from config manager like we are getting in main class?

 

Thanks for sharing the info about HttpClient test..

 

Avatar

Level 2

Hi @BrianKasingli , 

Thank you for sharing this, it doesn't have any Http request details. My request contains an external API and also with OSGi configurations defined in console.
This is some helpful solution, but OSGI configuration variables are predefined in Test class, in my case i do not want to define them in my main servlet or test class. Could you help me how we can do that? 
https://myaemlearnings.blogspot.com/2020/07/unit-testing-hands-on-for-sling-servlet.html