Fading Coder

One Final Commit for the Last Sprint

Home > Tech > Content

Building a REST API Automation Testing Framework with TestNG and Maven

Tech 1

Framework Architecture

This framework manages test parameters through XML configuration files, encapsulates HTTP requests using HttpClient, and orchestrates test execution through TestNG parameterized tests. ReportNG generates test reports while Maven handles project dependencies and build processes.

Key Components

  • xmlUtil: Parses XML configuration files to load test parameters
  • HttpUtils: Encapsulates HTTP request handling with cookie support
  • ResponseBean: Standardized API response object
  • ResponseUtil: Converts HTTP responses into ResponseBean instances
  • TestNG: Manages test execution flow and parameter injection

Project Setup

Required Dependencies

<dependencies>
    <!-- Apache HttpClient -->
    <dependency>
        <groupId>org.apache.httpcomponents</groupId>
        <artifactId>httpclient</artifactId>
        <version>4.5.13</version>
    </dependency>
    
    <!-- DOM4J for XML parsing -->
    <dependency>
        <groupId>org.dom4j</groupId>
        <artifactId>dom4j</artifactId>
        <version>2.1.3</version>
    </dependency>
    
    <!-- TestNG -->
    <dependency>
        <groupId>org.testng</groupId>
        <artifactId>testng</artifactId>
        <version>7.5</version>
    </dependency>
    
    <!-- ReportNG for enhanced reporting -->
    <dependency>
        <groupId>org.uncommons</groupId>
        <artifactId>reportng</artifactId>
        <version>1.2.4</version>
    </dependency>
</dependencies>

XML Cofniguration Management

Test parameters are stored in XML files for easy maintenance and modification without recompilation.

Parameter XML Structure

<?xml version="1.0" encoding="UTF-8"?>
<map>
    <bean beanName="GetRole">
        <locator name="page" value="1"/>
        <locator name="rows" value="10"/>
    </bean>
    <bean beanName="LoginIn">
        <locator name="username" value="admin"/>
        <locator name="password" value="admin123"/>
    </bean>
</map>

XML Parser Implementation

import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;

import java.io.File;
import java.util.HashMap;
import java.util.List;

public class XmlParameterReader {
    
    private static XmlParameterReader instance;
    
    private XmlParameterReader() {}
    
    public static XmlParameterReader getInstance() {
        if (instance == null) {
            instance = new XmlParameterReader();
        }
        return instance;
    }
    
    public HashMap<String, String> parseParameters(String beanName, String xmlFileName) {
        HashMap<String, String> params = new HashMap<>();
        SAXReader reader = new SAXReader();
        
        try {
            String resourcePath = Thread.currentThread().getContextClassLoader()
                    .getResource(xmlFileName).getPath();
            Document document = reader.read(new File(resourcePath));
            Element root = document.getRootElement();
            
            List<Element> beans = root.elements("bean");
            for (Element bean : beans) {
                if (beanName.equals(bean.attributeValue("beanName"))) {
                    List<Element> locators = bean.elements("locator");
                    for (Element locator : locators) {
                        String key = locator.attributeValue("name");
                        String value = locator.attributeValue("value");
                        params.put(key, value);
                    }
                }
            }
        } catch (DocumentException e) {
            throw new RuntimeException("Failed to parse XML: " + xmlFileName, e);
        }
        return params;
    }
}

HTTP Request Handling

HTTP Utility Class

import org.apache.http.HttpResponse;
import org.apache.http.client.CookieStore;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;

import java.io.IOException;
import java.util.Map;

public class HttpRequestHandler {
    
    private static HttpRequestHandler instance;
    
    private HttpRequestHandler() {}
    
    public static HttpRequestHandler getInstance() {
        if (instance == null) {
            instance = new HttpRequestHandler();
        }
        return instance;
    }
    
    public HttpResponse sendPost(String url, Map<String, String> params, 
                                  CloseableHttpClient client, CookieStore store) throws IOException {
        HttpPost request = new HttpPost(url);
        
        if (params != null && !params.isEmpty()) {
            List<BasicNameValuePair> paramList = new ArrayList<>();
            for (Map.Entry<String, String> entry : params.entrySet()) {
                paramList.add(new BasicNameValuePair(entry.getKey(), entry.getValue()));
            }
            request.setEntity(new UrlEncodedFormEntity(paramList, "UTF-8"));
        }
        
        return client.execute(request);
    }
    
    public HttpResponse sendGet(String url, CloseableHttpClient client) throws IOException {
        HttpGet request = new HttpGet(url);
        return client.execute(request);
    }
}

Response Handling

Response Bean

public class ApiResponse {
    private String status;
    private String statusCode;
    private String contentType;
    private String responseBody;
    private String requestUrl;
    private String requestMethod;
    private Map<String, String> cookies;
    
    // Getters and setters
    public String getStatus() { return status; }
    public void setStatus(String status) { this.status = status; }
    public String getStatusCode() { return statusCode; }
    public void setStatusCode(String statusCode) { this.statusCode = statusCode; }
    public String getContentType() { return contentType; }
    public void setContentType(String contentType) { this.contentType = contentType; }
    public String getResponseBody() { return responseBody; }
    public void setResponseBody(String responseBody) { this.responseBody = responseBody; }
    public String getRequestUrl() { return requestUrl; }
    public void setRequestUrl(String requestUrl) { this.requestUrl = requestUrl; }
    public String getRequestMethod() { return requestMethod; }
    public void setRequestMethod(String requestMethod) { this.requestMethod = requestMethod; }
    public Map<String, String> getCookies() { return cookies; }
    public void setCookies(Map<String, String> cookies) { this.cookies = cookies; }
}

Response Converter

import org.apache.http.Header;
import org.apache.http.HttpResponse;
import org.apache.http.client.CookieStore;
import org.apache.http.cookie.Cookie;
import org.apache.http.util.EntityUtils;

import java.util.HashMap;
import java.util.Map;

public class ResponseConverter {
    
    public ApiResponse convert(HttpResponse httpResponse) {
        ApiResponse response = new ApiResponse();
        
        response.setStatus(httpResponse.getStatusLine().getReasonPhrase());
        response.setStatusCode(String.valueOf(httpResponse.getStatusLine().getStatusCode()));
        
        if (httpResponse.getEntity() != null) {
            try {
                response.setResponseBody(EntityUtils.toString(httpResponse.getEntity(), "UTF-8"));
            } catch (IOException e) {
                response.setResponseBody("");
            }
        }
        
        Header contentTypeHeader = httpResponse.getFirstHeader("Content-Type");
        if (contentTypeHeader != null) {
            response.setContentType(contentTypeHeader.getValue());
        }
        
        return response;
    }
    
    public Map<String, String> extractCookies(CookieStore cookieStore) {
        Map<String, String> cookieMap = new HashMap<>();
        for (Cookie cookie : cookieStore.getCookies()) {
            cookieMap.put(cookie.getName(), cookie.getValue());
        }
        return cookieMap;
    }
}

Cookie Management

import org.apache.http.client.CookieStore;
import org.apache.http.impl.client.BasicCookieStore;
import org.apache.http.impl.cookie.BasicClientCookie;

public class CookieManager {
    
    private CookieStore cookieStore;
    
    public CookieManager() {
        this.cookieStore = new BasicCookieStore();
    }
    
    public CookieStore getCookieStore() {
        return this.cookieStore;
    }
    
    public void addCookie(String name, String value, String domain, String path) {
        BasicClientCookie cookie = new BasicClientCookie(name, value);
        cookie.setDomain(domain);
        cookie.setPath(path);
        this.cookieStore.addCookie(cookie);
    }
    
    public void clearCookies() {
        this.cookieStore.clear();
    }
}

Test Case Implementation

Test Class with TestNG

import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.testng.Assert;
import org.testng.annotations.*;

import java.io.IOException;
import java.util.Map;

public class RoleServiceTest {
    
    private CloseableHttpClient httpClient;
    private CookieManager cookieManager;
    private XmlParameterReader xmlReader;
    private HttpRequestHandler httpHandler;
    
    @BeforeSuite
    @Parameters({"baseUrl", "serviceBeanName", "expectedStatus", "configFile"})
    public void initializeSession(String baseUrl, String serviceBeanName, 
                                   String expectedStatus, String configFile) {
        cookieManager = new CookieManager();
        xmlReader = XmlParameterReader.getInstance();
        httpHandler = HttpRequestHandler.getInstance();
        
        Map<String, String> loginParams = xmlReader.parseParameters(serviceBeanName, configFile);
        httpClient = HttpClients.createDefault();
        
        String loginUrl = baseUrl + "/sys/login";
        try {
            org.apache.http.HttpResponse response = httpHandler.sendPost(
                loginUrl, loginParams, httpClient, cookieManager.getCookieStore());
            cookieManager.setCookieStoreFromResponse(response);
        } catch (IOException e) {
            throw new RuntimeException("Login failed", e);
        }
    }
    
    @Test(priority = 1)
    @Parameters({"baseUrl", "apiPath", "beanName", "expectedCode", "expectedBody", "configFile"})
    public void verifyGetRoleList(String baseUrl, String apiPath, String beanName,
                                  String expectedCode, String expectedBody, String configFile) {
        Map<String, String> requestParams = xmlReader.parseParameters(beanName, configFile);
        
        httpClient = HttpClients.custom()
                .setDefaultCookieStore(cookieManager.getCookieStore())
                .build();
        
        String requestUrl = baseUrl + apiPath;
        org.apache.http.HttpResponse response = null;
        try {
            response = httpHandler.sendPost(requestUrl, requestParams, httpClient, 
                                             cookieManager.getCookieStore());
        } catch (IOException e) {
            Assert.fail("API request failed: " + e.getMessage());
        }
        
        ApiResponse apiResponse = new ResponseConverter().convert(response);
        
        Assert.assertEquals("OK", apiResponse.getStatus(), "Status mismatch");
        Assert.assertEquals(expectedCode, apiResponse.getStatusCode(), "Status code mismatch");
        Assert.assertTrue(apiResponse.getResponseBody().contains(expectedBody), 
                          "Response body does not contain expected content");
    }
    
    @AfterSuite
    public void cleanup() {
        if (httpClient != null) {
            try {
                httpClient.close();
            } catch (IOException e) {
                // ignore cleanup errors
            }
        }
    }
}

TestNG Configuration File

<?xml version="1.0" encoding="UTF-8"?>
<suite name="RoleServiceTestSuite" parallel="classes" thread-count="5">
    <parameter name="baseUrl" value="http://localhost:8080/api"/>
    <parameter name="serviceBeanName" value="LoginIn"/>
    <parameter name="expectedStatus" value="OK"/>
    <parameter name="configFile" value="testParams.xml"/>
    
    <test name="RoleServiceTests" preserve-order="true">
        <parameter name="baseUrl" value="http://localhost:8080/api"/>
        <parameter name="apiPath" value="/json/getRoleInfo"/>
        <parameter name="beanName" value="GetRole"/>
        <parameter name="expectedCode" value="200"/>
        <parameter name="expectedBody" value="roleName"/>
        <parameter name="configFile" value="testParams.xml"/>
        
        <classes>
            <class name="com.test.api.RoleServiceTest">
                <methods>
                    <include name="verifyGetRoleList"/>
                </methods>
            </class>
        </classes>
    </test>
</suite>

Maven Configuration

<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
                             http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.test</groupId>
    <artifactId>api-automation-framework</artifactId>
    <version>1.0-SNAPSHOT</version>
    
    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
    </properties>
    
    <dependencies>
        <!-- Apache HttpClient -->
        <dependency>
            <groupId>org.apache.httpcomponents</groupId>
            <artifactId>httpclient</artifactId>
            <version>4.5.13</version>
        </dependency>
        
        <!-- TestNG -->
        <dependency>
            <groupId>org.testng</groupId>
            <artifactId>testng</artifactId>
            <version>7.5</version>
        </dependency>
        
        <!-- DOM4J -->
        <dependency>
            <groupId>org.dom4j</groupId>
            <artifactId>dom4j</artifactId>
            <version>2.1.3</version>
        </dependency>
        
        <!-- ReportNG -->
        <dependency>
            <groupId>org.uncommons</groupId>
            <artifactId>reportng</artifactId>
            <version>1.2.4</version>
        </dependency>
    </dependencies>
    
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>2.19</version>
                <configuration>
                    <suiteXmlFiles>
                        <suiteXmlFile>src/test/java/testSuites/TestSuite.xml</suiteXmlFile>
                    </suiteXmlFiles>
                </configuration>
            </plugin>
            
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.8.1</version>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                </configuration>
            </plugin>
            
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>2.5</version>
                <configuration>
                    <properties>
                        <property>
                            <name>usedefaultlisteners</name>
                            <value>false</value>
                        </property>
                        <property>
                            <name>listener</name>
                            <value>org.uncommons.reportng.HTMLReporter</value>
                        </property>
                    </properties>
                    <workingDirectory>target/</workingDirectory>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

Execution

Run tests via Maven:

mvn clean test

Alternatively, run directly from Eclipse by right-clicking the TestNG XML file and selecting "Run As > TestNG Suite".

Test reports are generated in target/surefire-reports with HTML formatting provided by ReportNG.

Related Articles

Understanding Strong and Weak References in Java

Strong References Strong reference are the most prevalent type of object referencing in Java. When an object has a strong reference pointing to it, the garbage collector will not reclaim its memory. F...

Comprehensive Guide to SSTI Explained with Payload Bypass Techniques

Introduction Server-Side Template Injection (SSTI) is a vulnerability in web applications where user input is improper handled within the template engine and executed on the server. This exploit can r...

Implement Image Upload Functionality for Django Integrated TinyMCE Editor

Django’s Admin panel is highly user-friendly, and pairing it with TinyMCE, an effective rich text editor, simplifies content management significantly. Combining the two is particular useful for bloggi...

Leave a Comment

Anonymous

◎Feel free to join the discussion and share your thoughts.