Data Driven API Testing with Katalon using Spreadsheet Data

In expanding my use of Katalon Studio, I have started working on automated API testing. Like Postman, you can set up a manual API test to confirm a request/response combination. But it's possible to take that further and not only send multiple API requests, but validate the response.

For a really good tutorial on putting together the API test, take a look at this video. It steps through creating the API object and configuring parameters.

Katalon Studio API Testing

Following up from that, the code listed below creates a data driven API test wherein it reads data from an XLSX spreadsheeting using the POI model.

The purpose of the test is to send a proper request for an inventory item and confirm the price of the item is correct. The spreadsheet contains the inventory data and the price of the item.

Additionally, the test records the Response Time and the Status Code of the request. Within Postman this would display as 398 ms 200 OK.

The request is built using data from the spreadsheet and is sent to the API object using the WS.sendRequest command.

Determining the number of rows in the spreadsheet is handled by:
int rowCount = sheet.getLastRowNum()

Reading data from the spreadsheet is done by reading a cell value and assigning it to a variable:
int customer_number=sheet.getRow(loop).getCell(0).getNumericCellValue()
String uom=sheet.getRow(loop).getCell(3).getStringCellValue();

The response time is recorded as:
api_response_time=api_request.getWaitingTime()

The status of the response is:
api_request.getStatusCode()

The response is parsed using the JsonSlurper
def api_response = slurper.parseText(api_request.getResponseBodyContent())

The different response items are found using the following method:
String itemPrice = api_response.pricing.tiers[0].price

Parsing the JSON correctly took a couple of tries as I was using pricing.tiers.price to traverse the hierarchy rather than seeing it as an array and using tiers[0].price. Using a site like http://jsonparseronline.com/ got things sorted out.

For output, the test records several pieces of information:
The total number of requests
The number of pricing errors along with status code errors
A finally tally of the success rate

Using the code below, 100 requests can be processed in less than 20 seconds
1500 items can be processed in 5 minutes

This test will continue to expand to validate more data points, but at the moment, it's a solid gauge of whether the API is working, the response time, and accuracy.

import groovy.json.JsonSlurper as JsonSlurper

import org.apache.poi.xssf.usermodel.XSSFCell
import org.apache.poi.xssf.usermodel.XSSFRow
import org.apache.poi.xssf.usermodel.XSSFSheet
import org.apache.poi.xssf.usermodel.XSSFWorkbook

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;

import com.kms.katalon.core.logging.KeywordLogger as KeywordLogger

import groovy.time.TimeCategory 
import groovy.time.TimeDuration

KeywordLogger log = new KeywordLogger()

def timeStart = new Date()
int responseTimeError, pricingError, statusCodeError, priceAvailableError, messageFailureError=0
int api_response_time
float successRate

def slurper = new JsonSlurper()

FileInputStream file = new FileInputStream ("//Postman Testing//Data Files//pricing.xlsx")
XSSFWorkbook workbook = new XSSFWorkbook(file);
//XSSFSheet sheet = workbook.getSheetAt(1);
XSSFSheet sheet = workbook.getSheet("pricing-100-items");
int rowCount = sheet.getLastRowNum()

'Read data from excel'
for (loop = 1; loop <=rowCount; loop++) {
    //Assign spreadsheet columns to variables
    int customer_number=sheet.getRow(loop).getCell(0).getNumericCellValue()
    int branch_number=sheet.getRow(loop).getCell(1).getNumericCellValue()
    String tier_code=sheet.getRow(loop).getCell(2).getStringCellValue();
    String uom=sheet.getRow(loop).getCell(3).getStringCellValue();
    String item_price=sheet.getRow(loop).getCell(4).getNumericCellValue();
    
    //Send API Request
    api_request = WS.sendRequest(findTestObject('Pricing Request', 
        [('customer_number') : customer_number, 
        ('branch_number') : branch_number, 
        ('tier_code') : tier_code, 
        ('uom') : uom]))
    
    //Store Response time
    api_response_time=api_request.getWaitingTime()
    
    //If the Status of the request is 200 (OK) process the response
    if (api_request.getStatusCode()==200){
        
        def api_response = slurper.parseText(api_request.getResponseBodyContent())
        //Store the item price
        String itemPrice = api_response.pricing.tiers[0].price
        //Verify there is no failure message
        String failure_message=api_response.pricing.tiers[0].failure_messages
        if (failure_message!='[]'){
            log.logError("ERROR: Failure Message on pricing request " + loop + " " + failure_message)
            messageFailureError++
        }
        
        //Verify that Price Available is true within the response
        String price_available = api_response.pricing.tiers[0].price_available
        if (price_available!="true"){
            log.logError("ERROR: Price Available error on pricing request " + loop + " " + price_available)
            priceAvailableError++
        }
        
        //Display pricing details
        log.logWarning('Tier Code: ' + tier_code + ' <---> ' + 'Item Price: ' + itemPrice + ' <---> ' + 'Expected Price: ' + item_price + ' <---> ' + 'Response time: ' + api_response_time)
        if (itemPrice!=item_price){
            log.logError("ERROR: The returned price for request " + loop + " does not match the expected price")
            pricingError++
        }
    } else {
        log.logError("ERROR: There was an error with the pricing request " + loop + ". Error code: " + api_request.getStatusCode())
        statusCodeError++
    }
    
    if (api_response_time>10000){
        log.logError("The server response time is higher than expected with a time of: " + api_response_time)
        responseTimeError++
    }
}

def timeStop = new Date()
TimeDuration duration = TimeCategory.minus(timeStop, timeStart)

log.logWarning("<--- API Pricing Request Results --->")
log.logWarning(rowCount + " items priced:")
log.logWarning("There were " + responseTimeError + " requests with a higher than average response time")
log.logWarning("There were " + pricingError + " requests with a different price than expected")
log.logWarning("There were " + statusCodeError + " requests with an unexpected status code")
log.logWarning("There were " + priceAvailableError + " requests with a Price Available error")
log.logWarning("There were " + messageFailureError + " requests with a Message Failure error")
successRate = ((rowCount - (Integer.valueOf(pricingError) + Integer.valueOf(statusCodeError))) / rowCount) * 100
log.logWarning("Success rate: " + successRate.round(2) +"%" )
log.logWarning("Execution Time: " + duration)

file.close();

Other articles of interest:

Leave a Reply

Your email address will not be published. Required fields are marked *

Recent Comments