Hello...
I would like to share my idea of automating RESTful API testing without using an automation tool.
This is quite interesting and challenging and has a very steep learning curve if took seriously, you can save thousands of Dollars to your organisations by developing your own Test-Automation framework for RESTful API testing.
The beautiful thing of this framework is, it is easy to maintain and can be integrated with all your continuous integration servers like Jenkins. The tests can be run on a nightly build and the TestNG report can be emailed to the concerned group of IDs.
There are many free API test libraries that would help you to automate your tests, but over a time the limitations stops your automation and you have to go for an alternative.
-----------------------------------------------------------------------------------------------------------
Things required to start:
1) Your favourite Java IDE, (I use Eclipse).
2) Apache Http Library (click
here to download).
3) Apache Excel POI (click
here to download).
4) TestNG (click
here to download), please download 'Ant/Maven', version of TestNG not the eclipse version.
5) ANT ( click
here to download), this is for build+run of your Java classes.
6) Gson (click
here to download), to handle/manipulate JSON response.
People who are good with Junit can replace TestNG with it. But I prefer TestNG because of its features.
It generates a beautiful HTML report at the end which gives an detailed info of tests passes/failed and skipped.
Below are the different Java classes each performing a unique task in automation.
1) BaseTasking.java : This class does the following,
- Data supply.
- Test Control.
- API Url creation dynamically.
- REST API calling.
- Response reading and formatting.
- Http connection managing.
2) DataReader.java : This is class reads the input parameters from a spread sheet and passes it to the data provider in the 'BaseTasking.java' class.
3) TestCollection.java : This class has a collection of tests.
4) RestulValidator.java : This class contains the logic for validating the results from an API call.
5) Testng.xml : This xml file will have the collection of test classes that are to be executed.
6) Build.xml : This xml file is used to compile and run the test-suite using Ant.
-----------------------------------------------------------------------------------------------------------
-----------------------------------------------------------------------------------------------------------
1) BaseTasking.java
package myTest;
import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.lang.reflect.Method;
import java.net.MalformedURLException;
import java.util.Properties;
import org.apache.http.HttpResponse;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.DefaultHttpClient;
import org.json.JSONArray;
import org.json.JSONException;
import org.testng.Reporter;
import org.testng.SkipException;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.DataProvider;
public class BaseTestFactors {
static DefaultHttpClient httpClient;
BufferedReader br=null;
HttpResponse response=null;
String URL=null;
String AuthenticationKey=null;
String TCNUM=null;
String Environment=null;
String MethodName=null;
String Stores=null;
String Products=null;
String Parameters=null;
String ExpectedJson=null;
public static String Execute=null;
public static String TestDescription="No description available for this test case now.. will be added soon";
@BeforeClass
public static void SetUpSuite(){
}
// every @Test method will call this data provider, and based on the Test method name they'll get their data.
@DataProvider(name = "MyData")
public static Object[][] dataProvider(Method meth) throws IOException {
System.out.println("inside the dataprovider class");
Properties prop = new Properties();
InputStream input = null;
int SheetNumber=0, TestNumber=0;
DataRead E=new DataRead();
try {
input = new FileInputStream("ReferenceFiles/TestDescription.properties");
prop.load(input);
TestDescription=(prop.getProperty(meth.getName().toString().trim()));
input = new FileInputStream("ReferenceFiles/Environment.properties");
prop.load(input);
String TestGroup=(prop.getProperty((meth.getName().toString()).substring(0,4)).trim());
String TestNumberString=((meth.getName().toString()).substring(5,7)).trim();
System.out.println("TCNUM "+TestNumberString);
TestNumber=Integer.parseInt(TestNumberString);
System.out.println(TestNumber);
SheetNumber=Integer.parseInt(TestGroup);
} catch (IOException ex) {
ex.printStackTrace();
}
catch (NullPointerException e) {
System.out.println("caught an exception while reading properties file");
}
String[][] StrData=E.ReadEXCEL(SheetNumber);
int Row=Excelread.RowSize;
int Col=Excelread.ColumSize;
Object[][] TempData = new Object[Row][Col];
Object[][] data= new Object[1][Col];
for(int x=0;x<Row;x++){
for(int y=0;y<Col;y++){
TempData[x][y]=StrData[x][y];
}
}
data[0]= TempData[(TestNumber-1)];
return data;
}
public void ExecutionCheck(){
if(Execute.equalsIgnoreCase("N"))
throw new SkipException("Skipping - This Test case was aksed to be SKIPPED ");
}
@SuppressWarnings("deprecation")
public String[] MyTestGerneral() throws JSONException{
System.out.println("InsideTest-"+TCNUM);
Reporter.log(TCNUM+":- "+TestDescription);
String CompleteURL=null;
String ValidationResults[]= new String[2];
Properties prop = new Properties();
InputStream input = null;
try {// Setter for TestEnvironment IP/FQDN
input = new FileInputStream("ReferenceFiles/Environment.properties");
prop.load(input);
String PropRead[]=(prop.getProperty(Environment).split(","));
System.out.println("property read"+PropRead[0]+" "+PropRead[1]);
URL=PropRead[0].toString();
AuthenticationKey=PropRead[1].toString();
CompleteURL=getURL();
System.out.println(CompleteURL);
} catch (IOException ex) {
ex.printStackTrace();
}
try {// Make HTTP Call
org.apache.http.Header[] headers=null;
String headerMessage= null;
String JExpected= ExpectedJson;
String JActual="[{\"NoJSON\":\"No\"}]";
System.out.println("before http call");
HttpResponse Temp_response=RESTCall(CompleteURL);
System.out.println(Temp_response.toString());
if (Temp_response != null){
System.out.println("after http call");
br = new BufferedReader(new InputStreamReader(Temp_response.getEntity().getContent()));
int actualRespCode=Temp_response.getStatusLine().getStatusCode();
if(actualRespCode != 200){
try{
headers = Temp_response.getHeaders("CustomMessage");
headerMessage= headers[0].toString();
}
catch(ArrayIndexOutOfBoundsException e)
{
headerMessage="NO Error Message in the response";
}
}
if(actualRespCode == 200){
JActual =GETJSONRESULT(br);
}
ResultValidator ResVal= new ResultValidator();
System.out.println("before validation");
ValidationResults=ResVal.Validate(TCNUM,JExpected.toString(),JActual.toString(),headerMessage,actualRespCode);
httpClient.getConnectionManager().shutdown();
}
} catch (ClientProtocolException e) {
e.printStackTrace();
} catch (NullPointerException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return ValidationResults;
}// end of myTestGeneral method
/*Method to the get the URL*/
public String getURL(){
int option=0;
String CompleteURL=null;
if(MethodName.equalsIgnoreCase("SS1-V1")) option=1;
else if(MethodName.equalsIgnoreCase("SS2-V1")) option=2;
else if(MethodName.equalsIgnoreCase("SS3-V1")) option=3;
else if(MethodName.equalsIgnoreCase("SS4-V1")) option=4;
else if(MethodName.equalsIgnoreCase("SS5-V1")) option=5;
else if(MethodName.equalsIgnoreCase("SS6-V1")) option=6;
else if(MethodName.equalsIgnoreCase("SS7-V1")) option=7;
else if(MethodName.equalsIgnoreCase("SS8-V1")) option=8;
else if(MethodName.equalsIgnoreCase("SS1-V2")) option=9;
else if(MethodName.equalsIgnoreCase("SS2-V2")) option=10;
else if(MethodName.equalsIgnoreCase("CRACK")) option=11;
else if(MethodName.equalsIgnoreCase("ROOT")) option=12;
else if(MethodName.equalsIgnoreCase("ACURCY")) option=13;
else if(MethodName.equalsIgnoreCase("ADJOIN")) option=14;
switch(option){
case 1:
CompleteURL="http://"+URL+"/v1/Stock/method1/"+Stores+"?"+"productIds="+Products+"&"+Parameters;
break;
case 2:
CompleteURL="http://"+URL+"/v1/stock/"+"method2/"+Products+"?"+"locationids="+Stores+"&"+Parameters;
break;
case 3:
CompleteURL="http://"+URL+"/v1/stock/"+"method3/"+Stores+"?"+"productIds="+Products+"&"+Parameters;
break;
case 4:
CompleteURL="http://"+URL+"/v1/Stock/"+"method4/"+Stores+"?"+"productIds="+Products+"&"+Parameters;
break;
case 5:
CompleteURL="http://"+URL+"/v1/stock/"+"method5/"+Products+"?"+"locationids="+Stores+"&"+Parameters;
break;
case 6:
CompleteURL="http://"+URL+"/v1/method6/all/"+Products+"/"+Stores+"?business=Group";
break;
case 7:
CompleteURL="http://"+URL+"/v1/stockcr/method7/"+Stores+"?productId="+Products+"&business=Group";
break;
case 8:
CompleteURL="http://"+URL+"/v1/stockcr/method8/"+Products+"?locationId="+Stores+"&business=Group";
break;
case 9:
CompleteURL="http://"+URL+"/v2/stock/method9?productIds="+Products+"&locationIds="+Stores;
break;
case 10:
CompleteURL="http://"+URL+"/v2/stock/method10?productIds="+Products+"&locationIds="+Stores;
break;
case 11:
CompleteURL="http://"+URL+"/v2/stock/method11?productIds="+Products+"&locationIds="+Stores+"&business=Group";
break;
case 12:
CompleteURL="http://"+URL+"/v2/stock/method12/"+Stores+"productIds="+Products;
break;
case 13:
CompleteURL="http://"+URL+"/v2/stock/method13?productIds="+Products+"&locationIds="+Stores;
break;
case 14:
CompleteURL="http://"+URL+"/v1/stockcr/method14/"+Stores+"?business=Group";
break;
case 0:
CompleteURL=null;
break;
}
return CompleteURL;
}
/*Method to call the rest method*/
public HttpResponse RESTCall(String URLData) throws IOException{
HttpGet getRequest = new HttpGet(URLData);
getRequest.addHeader("accept", "application/json");
getRequest.addHeader("Authorization", AuthenticationKey);
httpClient = new DefaultHttpClient();
response = httpClient.execute(getRequest);
return response;
}
/*Method to return the JSON Array result*/
public String GETJSONRESULT(BufferedReader Buff) throws IOException, JSONException{
String output=null;
String JasonResponse="";
System.out.println("Output from Server .... \n");
while ((output = Buff.readLine()) != null) {
System.out.println(output);
JasonResponse=JasonResponse+output.toString();
}
System.out.println(JasonResponse);
//JSONArray temp1 = new JSONArray(JasonResponse);
return JasonResponse;
}
public String[] MyPOSTModule() throws JSONException{// Method for POSTing the JSON data.
System.out.println("InsideTest-"+TCNUM);
Reporter.log(TCNUM+":- "+TestDescription);
String CompleteURL=null;
String ValidationResults[]= new String[2];
ValidationResults[0]="PASS";ValidationResults[1]="Looks SUccessfull";
Properties prop = new Properties();
InputStream input = null;
try {// Setter for Environment Variable
input = new FileInputStream("ReferenceFiles/Environment.properties");
prop.load(input);
String PropRead[]=(prop.getProperty(Environment).split(","));
System.out.println("property read"+PropRead[0]+" "+PropRead[1]);
URL=PropRead[0].toString();
AuthenticationKey=PropRead[1].toString();
CompleteURL=getURL();
System.out.println(CompleteURL);
} catch (IOException ex) {
ex.printStackTrace();
}
try {
DefaultHttpClient httpClient = new DefaultHttpClient();
FileInputStream inStrm = new FileInputStream("ReferenceFiles\\PostMethodJSON\\"+TCNUM+".txt");
byte[] fileData = new byte[input.available()];
inStrm.read(fileData);
inStrm.close();
String JSONData= new String(fileData, "UTF-8");
HttpPost postRequest = new HttpPost(CompleteURL);
postRequest.addHeader("Authorization", AuthenticationKey);
StringEntity StockJSON = new StringEntity(JSONData);
StockJSON.setContentType("application/json");
postRequest.setEntity(StockJSON);
HttpResponse response = httpClient.execute(postRequest);
if (response.getStatusLine().getStatusCode() != 200) {
ValidationResults[0]="FAIL";
//throw new RuntimeException("Failed : HTTP error code : "+ response.getStatusLine().getStatusCode());
}
BufferedReader br = new BufferedReader(new InputStreamReader((response.getEntity().getContent())));
String output="", TempOutput="";
System.out.println("Output from Server .... \n");
while ((output = br.readLine()) != null) {
TempOutput=TempOutput+output;
}
System.out.println(output);
ValidationResults[1]="Could not post the message - Please check manually: "+TempOutput;
httpClient.getConnectionManager().shutdown();
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return ValidationResults;
}
}
-----------------------------------------------------------------------------------------------------------
-----------------------------------------------------------------------------------------------------------
2) DataReader.java
package myTest;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import org.apache.poi.hssf.usermodel.HSSFCell;
import org.apache.poi.hssf.usermodel.HSSFRow;
import org.apache.poi.hssf.usermodel.HSSFSheet;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.poifs.filesystem.*;
public class DataRead{
public static int RowSize,ColumSize;
public String[][]ReadEXCEL(int sheetNum) throws IOException{
String fileName = "ReferenceFiles\\DataSheet.xls";
FileInputStream myInput = new FileInputStream(fileName);
POIFSFileSystem myFileSystem = new POIFSFileSystem(myInput);
HSSFWorkbook myWorkBook = new HSSFWorkbook(myFileSystem);
HSSFSheet mySheet = myWorkBook.getSheetAt(sheetNum);
RowSize= (mySheet.getLastRowNum());
HSSFRow myRow = mySheet.getRow(0);
ColumSize=(myRow.getLastCellNum());
String ExcelData[][]=new String[RowSize][ColumSize];
try{
for(int i=1;i<=RowSize;i++){
myRow= mySheet.getRow(i);
for(int j=0;j<ColumSize;j++){
HSSFCell C2 = myRow.getCell(j);
if(C2 !=null)
ExcelData[i-1][j]=C2.toString();
else
ExcelData[i-1][j]="";
}
}
}catch(NullPointerException e){
e.printStackTrace();
}
myInput.close();
return ExcelData;
}
}
-----------------------------------------------------------------------------------------------------------
-----------------------------------------------------------------------------------------------------------
3) TestCollection.java
package myTest;
import java.io.IOException;
import org.testng.AssertJUnit;
import org.testng.annotations.AfterClass;
import org.testng.annotations.Test;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeMethod;
import org.json.JSONException;
public class TestCollection extends BaseTasking {
@BeforeMethod
public void SetUpTest(){
}
@Test(dataProvider="MyData",enabled=true)//Test-001
public void TC01_01(String TcNumber,String Environment,String MethodName,String Parameter1,
String Parameter1, String OtherParameters,String Execute) throws JSONException, IOException{
super.TCNUM= TcNumber;super.Environment=Environment.trim();super.MethodName=MethodName.trim();super.Parameter1=parameter1.trim();super.Parameter2=Parameter2.trim();super.OtherParameters=OtherParameters.trim();super.Execute=Execute;
ExecutionCheck();
String Status[]=CallHttpMethod();
AssertJUnit.assertTrue(Status[1],Status[0].equalsIgnoreCase("PASS"));
}
@Test(dataProvider="MyData",enabled=true)//Test-002
public void TC01_02(String TcNumber,String Environment,String MethodName,String Parameter1,
String Parameter1, String OtherParameters,String Execute) throws JSONException, IOException{
super.TCNUM= TcNumber;super.Environment=Environment.trim();super.MethodName=MethodName.trim();super.Parameter1=parameter1.trim();super.Parameter2=Parameter2.trim();super.OtherParameters=OtherParameters.trim();super.Execute=Execute;
ExecutionCheck();
String Status[]=CallHttpMethod();
AssertJUnit.assertTrue(Status[1],Status[0].equalsIgnoreCase("PASS"));
}
@Test(dataProvider="MyData",enabled=true)//Test-003
public void TC01_03(String TcNumber,String Environment,String MethodName,String Parameter1,
String Parameter1, String OtherParameters,String Execute) throws JSONException, IOException{
super.TCNUM= TcNumber;super.Environment=Environment.trim();super.MethodName=MethodName.trim();super.Parameter1=parameter1.trim();super.Parameter2=Parameter2.trim();super.OtherParameters=OtherParameters.trim();super.Execute=Execute;
ExecutionCheck();
String Status[]=CallHttpMethod();
AssertJUnit.assertTrue(Status[1],Status[0].equalsIgnoreCase("PASS"));
}
@Test(dataProvider="MyData",enabled=true)//Test-004
public void TC01_04(String TcNumber,String Environment,String MethodName,String Parameter1,
String Parameter1, String OtherParameters,String Execute) throws JSONException, IOException{
super.TCNUM= TcNumber;super.Environment=Environment.trim();super.MethodName=MethodName.trim();super.Parameter1=parameter1.trim();super.Parameter2=Parameter2.trim();super.OtherParameters=OtherParameters.trim();super.Execute=Execute;
ExecutionCheck();
String Status[]=CallHttpMethod();
AssertJUnit.assertTrue(Status[1],Status[0].equalsIgnoreCase("PASS"));
}
@AfterMethod
public void TearDown() {
System.out.println("************************************End of Test"+TCNUM+"************************************\n\n");
}
@AfterClass
public static void TearDownTest(){
System.out.println("Test Complete");
}
}
-----------------------------------------------------------------------------------------------------------
-----------------------------------------------------------------------------------------------------------
4) RestulValidator.java
package myTest;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
public class ResultValidator {
public String[] Validate(String testcaseNum, String ExpectedJson,String ActualJson,
String HeaderMessage, int ActualRespCode ) throws JSONException{
String Flag="Fail";
String ReportMessage="";
switch(testcaseNum){
case TC01_01:
{
/*
Logic for validating your Actual results against the expected results.
use GSON to extract JSON value and use it to compare against the expected values.
use flags and indicators to set the validation status to 'Pass' or 'Fail'
do not use assertions, instead use if-else to compare the results.
finally return a 1D string array with
Str[0] having the value 'Pass'/'Fail'--> Flag
Str[1] having the error message detailing why the validation failed--> ReportMessage
*/
break;
}
case 2:
{
/*
Logic for validating your Actual results against the expected results.
use GSON to extract JSON value and use it to compare against the expected values.
use flags and indicators to set the validation status to 'Pass' or 'Fail'
do not use assertions, instead use if-else to compare the results.
finally return a 1D string array with
Str[0] having the value 'Pass'/'Fail'--> Flag
Str[1] having the error message detailing why the validation failed--> ReportMessage
*/
break;
}
case 3:
{ /*
Logic for validating your Actual results against the expected results.
use GSON to extract JSON value and use it to compare against the expected values.
use flags and indicators to set the validation status to 'Pass' or 'Fail'
do not use assertions, instead use if-else to compare the results.
finally return a 1D string array with
Str[0] having the value 'Pass'/'Fail'--> Flag
Str[1] having the error message detailing why the validation failed--> ReportMessage
*/
break;
}
case 4:
{/*
Logic for validating your Actual results against the expected results.
use GSON to extract JSON value and use it to compare against the expected values.
use flags and indicators to set the validation status to 'Pass' or 'Fail'
do not use assertions, instead use if-else to compare the results.
finally return a 1D string array with
Str[0] having the value 'Pass'/'Fail'--> Flag
Str[1] having the error message detailing why the validation failed--> ReportMessage
*/
break;
}// End of Group-4
default:
{
System.out.println("invalid Test case Number");
}
}//End of Switch
Flags[0]=Flag;
Flags[1]=ReportMessage;
return Flags;
}
}
-----------------------------------------------------------------------------------------------------------
-----------------------------------------------------------------------------------------------------------
4) Testng.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd">
<suite name="StockRegression" parallel="none">
<test name="Test">
<classes>
<class name="myTest.TestCollection"/>
</classes>
</test>
</suite>
-----------------------------------------------------------------------------------------------------------
-----------------------------------------------------------------------------------------------------------
Run this 'Testng.xml' using the TestNG run command via command-line.
you'll get an html report once all the tests are run.