Back to Articles
AutomationIntermediate

Hands-On: Building Your First API Automation Framework

Step-by-step guide to building a production-ready REST API test automation framework with Java, RestAssured, and TestNG

9 min read
...
api-testingrestassuredjavatestngautomation-frameworkhands-on
Banner for Hands-On: Building Your First API Automation Framework

What You'll Build

A complete API test automation framework for an e-commerce platform with:

  • ✅ Reusable REST API client
  • ✅ Test data management
  • ✅ Configurable environments
  • ✅ Comprehensive assertions
  • ✅ Test reporting
  • ✅ CI/CD integration

Time to complete: 2-3 hours
Skill level: Basic Java knowledge required

Prerequisites

# Required installations
- Java 11 or higher
- Maven 3.6+
- IDE (IntelliJ IDEA recommended)
- Git

Step 1: Project Setup

Create Maven Project

# Create project directory
mkdir api-automation-framework
cd api-automation-framework
 
# Initialize Maven project
mvn archetype:generate \
  -DgroupId=com.company.qe \
  -DartifactId=api-tests \
  -DarchetypeArtifactId=maven-archetype-quickstart \
  -DinteractiveMode=false
 
cd api-tests

Configure pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<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.company.qe</groupId>
    <artifactId>api-tests</artifactId>
    <version>1.0-SNAPSHOT</version>
 
    <properties>
        <maven.compiler.source>11</maven.compiler.source>
        <maven.compiler.target>11</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <restassured.version>5.3.2</restassured.version>
        <testng.version>7.8.0</testng.version>
        <jackson.version>2.15.3</jackson.version>
        <lombok.version>1.18.30</lombok.version>
    </properties>
 
    <dependencies>
        <!-- REST Assured for API testing -->
        <dependency>
            <groupId>io.rest-assured</groupId>
            <artifactId>rest-assured</artifactId>
            <version>${restassured.version}</version>
        </dependency>
 
        <!-- TestNG for test execution -->
        <dependency>
            <groupId>org.testng</groupId>
            <artifactId>testng</artifactId>
            <version>${testng.version}</version>
        </dependency>
 
        <!-- Jackson for JSON processing -->
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
            <version>${jackson.version}</version>
        </dependency>
 
        <!-- Lombok to reduce boilerplate -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>${lombok.version}</version>
            <scope>provided</scope>
        </dependency>
 
        <!-- AssertJ for fluent assertions -->
        <dependency>
            <groupId>org.assertj</groupId>
            <artifactId>assertj-core</artifactId>
            <version>3.24.2</version>
        </dependency>
 
        <!-- SLF4J for logging -->
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-simple</artifactId>
            <version>2.0.9</version>
        </dependency>
    </dependencies>
 
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>3.2.1</version>
                <configuration>
                    <suiteXmlFiles>
                        <suiteXmlFile>testng.xml</suiteXmlFile>
                    </suiteXmlFiles>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

Step 2: Create Project Structure

# Create directory structure
mkdir -p src/test/java/com/company/qe/{api,models,config,utils,tests}
mkdir -p src/test/resources

Resulting structure:

api-tests/
├── pom.xml
└── src/test/
    ├── java/com/company/qe/
    │   ├── api/          # API clients
    │   ├── models/       # Request/Response POJOs
    │   ├── config/       # Configuration
    │   ├── utils/        # Helper utilities
    │   └── tests/        # Test classes
    └── resources/
        ├── config.properties
        └── testng.xml

Step 3: Configuration Management

Create config.properties

# src/test/resources/config.properties
 
# Environment
env=qa
 
# QA Environment
qa.base.url=https://api-qa.example.com
qa.api.version=v1
 
# Staging Environment
staging.base.url=https://api-staging.example.com
staging.api.version=v1
 
# Test User Credentials
test.user.email=test@example.com
test.user.password=Test123!
 
# API Timeouts
api.timeout.connect=5000
api.timeout.read=10000

Create ConfigManager

// src/test/java/com/company/qe/config/ConfigManager.java
package com.company.qe.config;
 
import java.io.FileInputStream;
import java.io.IOException;
import java.util.Properties;
 
public class ConfigManager {
    private static Properties properties;
    private static String environment;
 
    static {
        loadProperties();
    }
 
    private static void loadProperties() {
        properties = new Properties();
        try {
            String configFile = "src/test/resources/config.properties";
            properties.load(new FileInputStream(configFile));
            environment = System.getProperty("env", properties.getProperty("env", "qa"));
        } catch (IOException e) {
            throw new RuntimeException("Failed to load configuration", e);
        }
    }
 
    public static String getBaseUrl() {
        return properties.getProperty(environment + ".base.url");
    }
 
    public static String getApiVersion() {
        return properties.getProperty(environment + ".api.version");
    }
 
    public static String getProperty(String key) {
        return properties.getProperty(key);
    }
 
    public static String getEnvironment() {
        return environment;
    }
}

Step 4: Create API Models

Product Model

// src/test/java/com/company/qe/models/Product.java
package com.company.qe.models;
 
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
 
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@JsonIgnoreProperties(ignoreUnknown = true)
public class Product {
    private String id;
    private String sku;
    private String name;
    private String description;
    private Double price;
    private Integer stock;
    private String category;
    private Boolean inStock;
}

Order Model

// src/test/java/com/company/qe/models/Order.java
package com.company.qe.models;
 
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
 
import java.util.List;
 
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@JsonIgnoreProperties(ignoreUnknown = true)
public class Order {
    private String id;
    private String userId;
    private List<OrderItem> items;
    private Double subtotal;
    private Double tax;
    private Double total;
    private String status;
    private String createdAt;
}
 
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@JsonIgnoreProperties(ignoreUnknown = true)
class OrderItem {
    private String productId;
    private String sku;
    private Integer quantity;
    private Double price;
}

User Model

// src/test/java/com/company/qe/models/User.java
package com.company.qe.models;
 
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
 
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@JsonIgnoreProperties(ignoreUnknown = true)
public class User {
    private String id;
    private String email;
    private String firstName;
    private String lastName;
    private String token;
}

Step 5: Build API Clients

Base API Client

// src/test/java/com/company/qe/api/BaseApiClient.java
package com.company.qe.api;
 
import com.company.qe.config.ConfigManager;
import io.restassured.RestAssured;
import io.restassured.builder.RequestSpecBuilder;
import io.restassured.filter.log.RequestLoggingFilter;
import io.restassured.filter.log.ResponseLoggingFilter;
import io.restassured.http.ContentType;
import io.restassured.specification.RequestSpecification;
 
public class BaseApiClient {
    protected RequestSpecification requestSpec;
 
    public BaseApiClient() {
        this.requestSpec = new RequestSpecBuilder()
                .setBaseUri(ConfigManager.getBaseUrl())
                .setBasePath("/api/" + ConfigManager.getApiVersion())
                .setContentType(ContentType.JSON)
                .addFilter(new RequestLoggingFilter())
                .addFilter(new ResponseLoggingFilter())
                .build();
    }
 
    protected RequestSpecification withAuth(String token) {
        return RestAssured.given(requestSpec)
                .header("Authorization", "Bearer " + token);
    }
}

Product API Client

// src/test/java/com/company/qe/api/ProductApiClient.java
package com.company.qe.api;
 
import com.company.qe.models.Product;
import io.restassured.RestAssured;
import io.restassured.response.Response;
 
import java.util.List;
 
public class ProductApiClient extends BaseApiClient {
 
    public Response getProducts() {
        return RestAssured.given(requestSpec)
                .when()
                .get("/products");
    }
 
    public Response getProductById(String productId) {
        return RestAssured.given(requestSpec)
                .pathParam("id", productId)
                .when()
                .get("/products/{id}");
    }
 
    public Response searchProducts(String query) {
        return RestAssured.given(requestSpec)
                .queryParam("q", query)
                .when()
                .get("/products/search");
    }
 
    public Response createProduct(Product product, String authToken) {
        return withAuth(authToken)
                .body(product)
                .when()
                .post("/products");
    }
 
    public Response updateProduct(String productId, Product product, String authToken) {
        return withAuth(authToken)
                .pathParam("id", productId)
                .body(product)
                .when()
                .put("/products/{id}");
    }
 
    public Response deleteProduct(String productId, String authToken) {
        return withAuth(authToken)
                .pathParam("id", productId)
                .when()
                .delete("/products/{id}");
    }
 
    // Helper method to convert response to Product list
    public List<Product> getProductsAsList(Response response) {
        return response.jsonPath().getList(".", Product.class);
    }
 
    // Helper method to convert response to single Product
    public Product getProductFromResponse(Response response) {
        return response.as(Product.class);
    }
}

Order API Client

// src/test/java/com/company/qe/api/OrderApiClient.java
package com.company.qe.api;
 
import com.company.qe.models.Order;
import io.restassured.response.Response;
 
public class OrderApiClient extends BaseApiClient {
 
    public Response createOrder(Order order, String authToken) {
        return withAuth(authToken)
                .body(order)
                .when()
                .post("/orders");
    }
 
    public Response getOrderById(String orderId, String authToken) {
        return withAuth(authToken)
                .pathParam("id", orderId)
                .when()
                .get("/orders/{id}");
    }
 
    public Response getUserOrders(String userId, String authToken) {
        return withAuth(authToken)
                .queryParam("userId", userId)
                .when()
                .get("/orders");
    }
 
    public Response updateOrderStatus(String orderId, String status, String authToken) {
        return withAuth(authToken)
                .pathParam("id", orderId)
                .body("{\"status\": \"" + status + "\"}")
                .when()
                .patch("/orders/{id}/status");
    }
 
    public Order getOrderFromResponse(Response response) {
        return response.as(Order.class);
    }
}

Step 6: Create Test Data Utilities

// src/test/java/com/company/qe/utils/TestDataGenerator.java
package com.company.qe.utils;
 
import com.company.qe.models.Product;
import com.company.qe.models.Order;
import com.company.qe.models.OrderItem;
 
import java.util.List;
import java.util.UUID;
 
public class TestDataGenerator {
 
    public static Product createTestProduct() {
        return Product.builder()
                .sku("TEST-SKU-" + UUID.randomUUID().toString().substring(0, 8))
                .name("Test Product " + System.currentTimeMillis())
                .description("This is a test product")
                .price(99.99)
                .stock(100)
                .category("Electronics")
                .inStock(true)
                .build();
    }
 
    public static Order createTestOrder(String userId, String productId) {
        OrderItem item = OrderItem.builder()
                .productId(productId)
                .sku("TEST-SKU-001")
                .quantity(2)
                .price(99.99)
                .build();
 
        return Order.builder()
                .userId(userId)
                .items(List.of(item))
                .subtotal(199.98)
                .tax(16.00)
                .total(215.98)
                .status("pending")
                .build();
    }
 
    public static String generateRandomEmail() {
        return "test" + System.currentTimeMillis() + "@example.com";
    }
}

Step 7: Write Your First Tests

Product API Tests

// src/test/java/com/company/qe/tests/ProductApiTest.java
package com.company.qe.tests;
 
import com.company.qe.api.ProductApiClient;
import com.company.qe.models.Product;
import com.company.qe.utils.TestDataGenerator;
import io.restassured.response.Response;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;
 
import java.util.List;
 
import static org.assertj.core.api.Assertions.assertThat;
import static org.hamcrest.Matchers.*;
 
public class ProductApiTest {
    private ProductApiClient productApi;
 
    @BeforeClass
    public void setup() {
        productApi = new ProductApiClient();
    }
 
    @Test(description = "Get all products successfully")
    public void testGetAllProducts() {
        Response response = productApi.getProducts();
 
        // Verify status code
        assertThat(response.statusCode()).isEqualTo(200);
 
        // Verify response has products
        List<Product> products = productApi.getProductsAsList(response);
        assertThat(products).isNotEmpty();
 
        // Verify first product has required fields
        Product firstProduct = products.get(0);
        assertThat(firstProduct.getId()).isNotNull();
        assertThat(firstProduct.getName()).isNotNull();
        assertThat(firstProduct.getPrice()).isGreaterThan(0);
    }
 
    @Test(description = "Search products by keyword")
    public void testSearchProducts() {
        String searchQuery = "laptop";
 
        Response response = productApi.searchProducts(searchQuery);
 
        response.then()
                .statusCode(200)
                .body("size()", greaterThan(0))
                .body("[0].name", containsStringIgnoringCase(searchQuery));
    }
 
    @Test(description = "Get product by ID")
    public void testGetProductById() {
        // First, get all products to get a valid ID
        Response allProducts = productApi.getProducts();
        List<Product> products = productApi.getProductsAsList(allProducts);
        String productId = products.get(0).getId();
 
        // Get specific product
        Response response = productApi.getProductById(productId);
        Product product = productApi.getProductFromResponse(response);
 
        assertThat(response.statusCode()).isEqualTo(200);
        assertThat(product.getId()).isEqualTo(productId);
        assertThat(product.getName()).isNotNull();
    }
 
    @Test(description = "Get non-existent product returns 404")
    public void testGetNonExistentProduct() {
        String fakeId = "non-existent-id-12345";
 
        Response response = productApi.getProductById(fakeId);
 
        assertThat(response.statusCode()).isEqualTo(404);
    }
 
    @Test(description = "Search with empty query returns all products")
    public void testSearchWithEmptyQuery() {
        Response response = productApi.searchProducts("");
 
        assertThat(response.statusCode()).isEqualTo(200);
        assertThat(productApi.getProductsAsList(response)).isNotEmpty();
    }
}

Order API Tests

// src/test/java/com/company/qe/tests/OrderApiTest.java
package com.company.qe.tests;
 
import com.company.qe.api.OrderApiClient;
import com.company.qe.models.Order;
import com.company.qe.utils.TestDataGenerator;
import io.restassured.response.Response;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;
 
import static org.assertj.core.api.Assertions.assertThat;
 
public class OrderApiTest {
    private OrderApiClient orderApi;
    private String authToken = "test-token-123"; // In real scenario, get from login
 
    @BeforeClass
    public void setup() {
        orderApi = new OrderApiClient();
    }
 
    @Test(description = "Create order successfully")
    public void testCreateOrder() {
        // Arrange
        Order testOrder = TestDataGenerator.createTestOrder("user-123", "product-456");
 
        // Act
        Response response = orderApi.createOrder(testOrder, authToken);
        Order createdOrder = orderApi.getOrderFromResponse(response);
 
        // Assert
        assertThat(response.statusCode()).isEqualTo(201);
        assertThat(createdOrder.getId()).isNotNull();
        assertThat(createdOrder.getStatus()).isEqualTo("pending");
        assertThat(createdOrder.getTotal()).isEqualTo(testOrder.getTotal());
    }
 
    @Test(description = "Get order by ID")
    public void testGetOrderById() {
        // Create order first
        Order testOrder = TestDataGenerator.createTestOrder("user-123", "product-456");
        Response createResponse = orderApi.createOrder(testOrder, authToken);
        String orderId = orderApi.getOrderFromResponse(createResponse).getId();
 
        // Get the order
        Response response = orderApi.getOrderById(orderId, authToken);
        Order retrievedOrder = orderApi.getOrderFromResponse(response);
 
        assertThat(response.statusCode()).isEqualTo(200);
        assertThat(retrievedOrder.getId()).isEqualTo(orderId);
    }
 
    @Test(description = "Update order status")
    public void testUpdateOrderStatus() {
        // Create order
        Order testOrder = TestDataGenerator.createTestOrder("user-123", "product-456");
        Response createResponse = orderApi.createOrder(testOrder, authToken);
        String orderId = orderApi.getOrderFromResponse(createResponse).getId();
 
        // Update status
        Response updateResponse = orderApi.updateOrderStatus(orderId, "shipped", authToken);
 
        assertThat(updateResponse.statusCode()).isEqualTo(200);
        assertThat(updateResponse.path("status").toString()).isEqualTo("shipped");
    }
}

Step 8: Configure TestNG

<!-- testng.xml -->
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd">
<suite name="API Test Suite" parallel="methods" thread-count="3">
    <test name="Product API Tests">
        <classes>
            <class name="com.company.qe.tests.ProductApiTest"/>
        </classes>
    </test>
    
    <test name="Order API Tests">
        <classes>
            <class name="com.company.qe.tests.OrderApiTest"/>
        </classes>
    </test>
</suite>

Step 9: Run Tests

# Run all tests
mvn clean test
 
# Run specific test class
mvn test -Dtest=ProductApiTest
 
# Run with specific environment
mvn test -Denv=staging
 
# Run with parallel execution
mvn test -DthreadCount=5

Step 10: CI/CD Integration

GitHub Actions

# .github/workflows/api-tests.yml
name: API Tests
 
on:
  push:
    branches: [ main, develop ]
  pull_request:
    branches: [ main ]
  schedule:
    - cron: '0 */6 * * *'  # Every 6 hours
 
jobs:
  test:
    runs-on: ubuntu-latest
 
    steps:
      - uses: actions/checkout@v3
      
      - name: Set up JDK 11
        uses: actions/setup-java@v3
        with:
          java-version: '11'
          distribution: 'temurin'
      
      - name: Cache Maven dependencies
        uses: actions/cache@v3
        with:
          path: ~/.m2
          key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }}
      
      - name: Run tests
        run: mvn clean test -Denv=qa
      
      - name: Publish test report
        if: always()
        uses: actions/upload-artifact@v3
        with:
          name: test-results
          path: target/surefire-reports/

Best Practices Implemented

Separation of Concerns: API clients, models, and tests are separate
Reusability: Base API client for common functionality
Configuration Management: Environment-specific config
Test Data Management: Utilities for generating test data
Clean Assertions: Using AssertJ for fluent assertions
Logging: Request/response logging for debugging
Parallel Execution: Tests run in parallel for speed
CI/CD Ready: GitHub Actions integration

Next Steps

  1. Add more test scenarios: Negative tests, edge cases
  2. Implement authentication: Real login flow
  3. Add test data cleanup: AfterMethod hooks
  4. Create test reports: Extent Reports or Allure
  5. Add performance tests: Measure response times
  6. Contract testing: Add Pact for API contracts
  7. Mock external services: Use WireMock

Conclusion

You now have a production-ready API automation framework! This framework follows industry best practices and can scale to hundreds of tests. Start adding tests for your platform's APIs and watch your test coverage grow.

Remember: Good test automation is maintainable, reliable, and provides fast feedback. Keep your tests focused, independent, and well-organized.

Happy testing! 🚀

Comments (0)

Loading comments...