Testing HTTPS Web Services With Cucumber
Let's make testing HTTPS web services easier with a Java REST client and Cucumber, including tips on configuring and customizing your tests.
Join the DZone community and get the full member experience.
Join For FreeIn this article, I am writing a small example on how to test HTTPS web services using a Java REST client in a Cucumber test.
Any Java developer can have to test web services on DEV, PRE-PRODUCTION, or PRODUCTION, and in order to avoid regression, BDD methodology is appreciated.
In this context, many feel quite good with Cucumber API.
In the example below, I'll use an HTTPS Java REST client that ignores certification in order to facilitate switching between environments and avoid "playing" with KeyStore files anywhere.
Let's begin with the Maven dependencies.
Cucumber dependency with test scope:
<dependency>
<groupId>info.cukes</groupId>
<artifactId>cucumber-java</artifactId>
<version>1.2.5</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>info.cukes</groupId>
<artifactId>cucumber-junit</artifactId>
<version>1.2.5</version>
<scope>test</scope>
</dependency>
Maven's HTTP client dependency:
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.3</version>
</dependency>
We'll need our famous HTTPS Java REST client ignoring HTTPS constraints. Note that you can also add a certificate exception to your local JVM.
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.lang.reflect.Type;
import java.net.URL;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.cert.Certificate;
import java.security.cert.X509Certificate;
import java.util.HashMap;
import java.util.Map;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLPeerUnverifiedException;
import javax.net.ssl.SSLSession;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import com.sun.org.apache.xml.internal.security.utils.Base64;
import com.ahajri.cucumber.rest.beans.WsResponse;
/**
* ERST Client ignoring HTTPS certificate
*
* @author ahajri
*
*/
public class HttpsClient {
private String username;
private String password;
public HttpsClient(String username, String password) {
super();
this.username = username;
this.password = password;
}
/**
*
* @param httpsUrl
* @return {@link WsResponse}
* @throws Exception
*/
public WsResponse doGet(String httpsUrl) throws Exception {
HttpsURLConnection connection = initHttpsConnection(httpsUrl, username, password);
BufferedReader br = new BufferedReader(new InputStreamReader(connection.getInputStream()));
int httpStatus = 500;
Map < String, Object > jsonMap = new HashMap < > ();
Type mapType = new TypeToken < Map < String, Object >> () {}.getType();
String json;
while ((json = br.readLine()) != null) {
jsonMap = new Gson().fromJson(json, mapType);
System.out.println(json);
httpStatus = Integer.parseInt((String) jsonMap.get("status"));
}
br.close();
connection.disconnect();
return new WsResponse(httpStatus, jsonMap);
}
/**
*
* @param https_url
* @param username
* @param password
* @return {@link HttpsURLConnection}
* @throws NoSuchAlgorithmException
* @throws KeyManagementException
* @throws IOException
*/
private HttpsURLConnection initHttpsConnection(String https_url, String username, String password)
throws NoSuchAlgorithmException, KeyManagementException, IOException {
URL url = new URL(https_url);
// Create a context that doesn't check certificates.
SSLContext ssl_ctx = SSLContext.getInstance("TLS");
TrustManager[] trust_mgr = getTrustMgr();
ssl_ctx.init(null, // key manager
trust_mgr, // trust manager
new SecureRandom()); // random number generator
HttpsURLConnection.setDefaultSSLSocketFactory(ssl_ctx.getSocketFactory());
HttpsURLConnection connection = (HttpsURLConnection) url.openConnection();
String userpass = username + ":" + password;
String basicAuth = "Basic " + new String(Base64.encode(userpass.getBytes()));
connection.setRequestProperty("Authorization", basicAuth);
// Guard against "bad hostname" errors during handshake.
connection.setHostnameVerifier(new HostnameVerifier() {
public boolean verify(String host, SSLSession sess) {
if (host.equals("localhost"))
return true;
else
return false;
}
});
return connection;
}
private void printHttpsCert(HttpsURLConnection connection) {
if (connection != null) {
try {
System.out.println("Response Code : " + connection.getResponseCode());
System.out.println("Cipher Suite : " + connection.getCipherSuite());
System.out.println("\n");
Certificate[] certs = connection.getServerCertificates();
for (Certificate cert: certs) {
System.out.println("Cert Type : " + cert.getType());
System.out.println("Cert Hash Code : " + cert.hashCode());
System.out.println("Cert Public Key Algorithm : " + cert.getPublicKey().getAlgorithm());
System.out.println("Cert Public Key Format : " + cert.getPublicKey().getFormat());
System.out.println("\n");
}
} catch (SSLPeerUnverifiedException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
private TrustManager[] getTrustMgr() {
TrustManager[] certs = new TrustManager[] {
new X509TrustManager() {
public X509Certificate[] getAcceptedIssuers() {
return null;
}
public void checkClientTrusted(X509Certificate[] certs, String t) {}
public void checkServerTrusted(X509Certificate[] certs, String t) {}
}
};
return certs;
}
}
WsResponse is a bean class that has the HTTP code and the mapped returned data. You can choose whatever you need:
import java.io.Serializable;
import java.util.Map;
/**
*
* @author ahajri
*
*/
public class WsResponse implements Serializable {
/**
*
*/
private static final long serialVersionUID = -4929529852038654613 L;
private int httpStatus;
private Map < String, Object > jsonMap;
public WsResponse(int httpStatus) {
super();
this.httpStatus = httpStatus;
}
public WsResponse(int httpStatus, Map < String, Object > jsonMap) {
super();
this.httpStatus = httpStatus;
this.jsonMap = jsonMap;
}
public int getHttpStatus() {
return httpStatus;
}
public void setHttpStatus(int httpStatus) {
this.httpStatus = httpStatus;
}
public Map < String, Object > getJsonMap() {
return jsonMap;
}
public void setJsonMap(Map < String, Object > jsonMap) {
this.jsonMap = jsonMap;
}
@Override
public String toString() {
return "WsResponse [httpStatus=" + httpStatus + ", jsonMap=" + jsonMap.toString() + "]";
}
}
Now let's configure our Cucumber feature. If you are not familiar with Cucumber, please have a look here. The feature file will look like this:
@call_preprod
Feature: xWebService - test PreProd Environment
Scenario: get some result
Given the following URL, Login, Password "https://someurl:25152/ws/api/v1/sometask/2017/2187AA36" AND "ws_user" AND "07cdec654efe"
When call https ws
Then found result
@call_preprod is the feature tag. This could help us when doing the same test in a different environment. For example, you can change the tag to ping the good URL. You can also see that I added the WS username/password in the @Given step, and you can change it for every feature file or scenario.
The test class looks like this:
import org.junit.runner.RunWith;
import cucumber.api.CucumberOptions;
import cucumber.api.junit.Cucumber;
/**
*
* @author ahajri
*
*/
@RunWith(Cucumber.class)
@CucumberOptions(features = { "classpath:feature/preprod.feature" }, strict = false, tags = { "@call_preprod" })
public class RunWsPreProdTest {
}
We defined features that define the local path to *.feature files. We can add more and prompt the tag for every test. The step definition class looks like:
import org.junit.Assert;
import cucumber.api.java.en.Given;
import cucumber.api.java.en.Then;
import cucumber.api.java.en.When;
import com.ahajri.cucumber.rest.beans.WsResponse;
import com.ahajri.cucumber.rest.utils.HttpsClient;
/**
*
* @author ahajri
*/
public class PreProdStepsDef {
private String someUrl;
private HttpsClient httpsClient;
private WsResponse getResult;
@Given("^the following URL, Login, Password \"(.*?)\" AND \"(.*?)\" AND \"(.*?)\"$")
public void the_following_URL_Login_Password(String url, String username, String password)
throws Throwable {
httpsClient = new HttpsClient(username, password);
someUrl = url;
}
@When("^call https ws$")
public void call_https_ws() throws Throwable {
getResult = httpsClient.doGet(someUrl);
}
@Then("^found result$")
public void found_result() throws Throwable {
Assert.assertNotNull(getResult);
Assert.assertEquals(200, getResult.getHttpStatus());
}
}
Hope this could be helpful!
Opinions expressed by DZone contributors are their own.
Comments