Welcome to this comprehensive guide on API Testing with RestAssured. Whether you’re a beginner looking to understand the fundamentals or an experienced tester aiming to refine your automation framework, this tutorial is tailored for you. We’ll walk through building a robust API automation framework from scratch, integrating essential tools and best practices to ensure maintainability, scalability, and efficiency.

1. Introduction

API testing is a crucial aspect of ensuring that your backend services function correctly, efficiently, and securely. RestAssured is a powerful Java library designed to simplify API testing, making it easier to validate HTTP responses and streamline the automation process.

In this tutorial, we’ll build an end-to-end API automation framework using RestAssured, TestNG, Maven, and Log4j2. We’ll also integrate GitHub Actions for continuous integration (CI), ensuring that our tests run automatically with each code change.

2. Setting Up the Maven Project

Before diving into writing tests, we need to set up our Maven project. Maven is a build automation tool that manages project dependencies, builds, and documentation.

Steps:

Initialize a Maven Project:

  • Use your IDE (like IntelliJ IDEA or Eclipse) to create a new Maven project.
  • Alternatively, use the command line:
mvn archetype:generate -DgroupId=com.example -DartifactId=RestAssuredAPITest -DarchetypeArtifactId=maven-archetype-quickstart -DinteractiveMode=false

Project Structure:

Maven projects follow a standardized directory structure:

RestAssuredAPITest
├── src
│   ├── main
│   │   └── java
│   └── test
│       └── java
└── pom.xml

Configure pom.xml:

Add necessary dependencies for RestAssured, TestNG, Log4j2, and Maven Surefire Plugin.

<!-- pom.xml -->
<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/maven-v4_0_0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.example</groupId>
    <artifactId>RestAssuredAPITest</artifactId>
    <version>1.0-SNAPSHOT</version>
    <dependencies>
        <!-- RestAssured -->
        <dependency>
            <groupId>io.rest-assured</groupId>
            <artifactId>rest-assured</artifactId>
            <version>5.3.0</version>
            <scope>test</scope>
        </dependency>
        <!-- TestNG -->
        <dependency>
            <groupId>org.testng</groupId>
            <artifactId>testng</artifactId>
            <version>7.7.0</version>
            <scope>test</scope>
        </dependency>
        <!-- Log4j2 -->
        <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-api</artifactId>
            <version>2.19.0</version>
        </dependency>
        <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-core</artifactId>
            <version>2.19.0</version>
        </dependency>
        <!-- MySQL Connector (for JDBC Connectivity) -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.30</version>
            <scope>test</scope>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <!-- Maven Surefire Plugin -->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>3.0.0-M7</version>
                <configuration>
                    <suiteXmlFiles>
                        <suiteXmlFile>${suiteXmlFile}</suiteXmlFile>
                    </suiteXmlFiles>
                </configuration>
            </plugin>
            <!-- Maven Compiler Plugin -->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.8.1</version>
                <configuration>
                    <source>11</source>
                    <target>11</target>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

Explanation

  • RestAssured: Facilitates API testing by providing a domain-specific language (DSL) for making HTTP requests and validating responses.
  • TestNG: A testing framework inspired by JUnit but with more powerful features like annotations, parallel execution, and flexible test configurations.
  • Log4j2: A reliable logging framework to capture detailed logs during test execution.
  • Maven Surefire Plugin: Executes TestNG tests during the Maven build lifecycle.
  • Maven Compiler Plugin: Specifies the Java version to ensure consistency across development environments.
  • MySQL Connector: Enables JDBC connectivity for database interactions, essential for data-driven testing.

3. Implementing the API Base Class

To promote code reusability and maintainability, we’ll create an API Base Class that serves as an abstraction over RestAssured. This class will handle common configurations and provide methods for HTTP operations.

Code Example

// src/test/java/com/example/base/APIBase.java
package com.example.base;

import com.example.filters.LoggingFilter;
import io.restassured.builder.RequestSpecBuilder;
import io.restassured.specification.RequestSpecification;
import io.restassured.RestAssured;
import io.restassured.response.Response;

public abstract class APIBase {

    protected static RequestSpecification requestSpec;

    static {
        // Initialize Request Specification
        requestSpec = new RequestSpecBuilder()
                .setBaseUri("https://api.example.com")
                .addHeader("Content-Type", "application/json")
                .addFilter(new LoggingFilter()) // Adding the custom LoggingFilter
                .build();
    }

    protected Response get(String endpoint) {
        return RestAssured.given()
                .spec(requestSpec)
                .get(endpoint);
    }

    protected Response post(String endpoint, Object body) {
        return RestAssured.given()
                .spec(requestSpec)
                .body(body)
                .post(endpoint);
    }

    protected Response put(String endpoint, Object body) {
        return RestAssured.given()
                .spec(requestSpec)
                .body(body)
                .put(endpoint);
    }

    protected Response delete(String endpoint) {
        return RestAssured.given()
                .spec(requestSpec)
                .delete(endpoint);
    }
}

Explanation

  • RequestSpecification: Centralizes common request configurations like base URI and headers.
  • HTTP Methods: Provides reusable methods (get, post, put, delete) to perform respective HTTP operations, reducing redundancy in test scripts.
  • Static Block: Ensures that the request specifications and filters are initialized once when the class is loaded, optimizing performance especially during parallel executions.

4. Creating Service Classes

Service classes represent different API services or controllers. Each service class extends the API Base Class and contains methods corresponding to specific API endpoints.

Code Example

// src/test/java/com/example/services/AuthService.java
package com.example.services;

import com.example.base.APIBase;
import com.example.models.LoginRequest;
import com.example.models.LoginResponse;
import io.restassured.response.Response;
import io.restassured.specification.RequestSpecification;
import io.restassured.RestAssured;

public class AuthService extends APIBase {

    private static final String LOGIN_ENDPOINT = "/auth/login";
    private static final String LOGOUT_ENDPOINT = "/auth/logout";

    public Response login(LoginRequest loginRequest) {
        return post(LOGIN_ENDPOINT, loginRequest);
    }

    public Response logout(String token) {
        // Assuming token is passed in headers for logout
        RequestSpecification modifiedSpec = RestAssured.given()
                .spec(requestSpec)
                .header("Authorization", "Bearer " + token);
        return RestAssured.given()
                .spec(modifiedSpec)
                .delete(LOGOUT_ENDPOINT);
    }
}

Explanation

  • AuthService: Manages authentication-related API calls like login and logout.
  • Endpoints: Defined as constants to avoid hardcoding and facilitate easy updates if endpoints change.
  • Methods: Utilize the HTTP methods from the API Base Class to perform actions, ensuring consistency and reusability.
  • Modified Request Specification: Allows adding dynamic headers (like Authorization) specific to certain requests without altering the global request specification.

5. Defining Models (POJOs) for Requests and Responses

To handle JSON payloads efficiently, we’ll create Plain Old Java Objects (POJOs) that map to the structure of API requests and responses.

Code Example

// src/test/java/com/example/models/LoginRequest.java
package com.example.models;

public class LoginRequest {
    private String username;
    private String password;

    // Constructor
    public LoginRequest(String username, String password) {
        this.username = username;
        this.password = password;
    }

    // Getters and Setters
    public String getUsername() { return username; }
    public void setUsername(String username) { this.username = username; }
    
    public String getPassword() { return password; }
    public void setPassword(String password) { this.password = password; }
}
// src/test/java/com/example/models/LoginResponse.java
package com.example.models;

public class LoginResponse {
    private String token;
    private String userId;
    private String message;

    // Getters and Setters
    public String getToken() { return token; }
    public void setToken(String token) { this.token = token; }
    
    public String getUserId() { return userId; }
    public void setUserId(String userId) { this.userId = userId; }
    
    public String getMessage() { return message; }
    public void setMessage(String message) { this.message = message; }
}

Explanation

  • LoginRequest: Represents the payload required for the login API, encapsulating username and password.
  • LoginResponse: Maps the expected response from the login API, including token, userId, and a message.
  • Encapsulation: Using private fields with public getters and setters ensures data integrity and adheres to object-oriented principles.

6. Logging with Log4j2

Effective logging is essential for debugging and monitoring test executions. We’ll configure Log4j2 to capture detailed logs of our API tests.

Configuring log4j2.xml

Create a log4j2.xml file in src/test/resources with the following content:

<!-- src/test/resources/log4j2.xml -->
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="WARN">
    <Appenders>
        <!-- Console Appender -->
        <Console name="ConsoleAppender" target="SYSTEM_OUT">
            <PatternLayout pattern="%d{HH:mm:ss} [%t] %-5level %c{1} - %msg%n"/>
        </Console>
        
        <!-- File Appender -->
        <File name="FileAppender" fileName="logs/test.log">
            <PatternLayout pattern="%d{HH:mm:ss} [%t] %-5level %c{1} - %msg%n"/>
        </File>
    </Appenders>
    <Loggers>
        <Root level="info">
            <AppenderRef ref="ConsoleAppender"/>
            <AppenderRef ref="FileAppender"/>
        </Root>
    </Loggers>
</Configuration>

Explanation

  • Appenders: Define where logs will be outputted.
  • ConsoleAppender: Displays logs in the console with a specific pattern.
  • FileAppender: Writes logs to logs/test.log, maintaining the same pattern as the console.
  • PatternLayout: Specifies the format of log messages.
    • %d{HH:mm:ss}: Timestamp in hours, minutes, and seconds.
    • [%t]: Thread name.
    • %-5level: Log level (INFO, DEBUG, ERROR, etc.).
    • %c{1}: Logger name (typically the class name).
    • %msg%n: Log message followed by a newline.
  • Root Logger: Captures all logs at the info level and above, directing them to both appenders.

Integrating Log4j2 in Tests

Initialize Log4j2 in your test classes to start logging.

// src/test/java/com/example/tests/LoginTest.java
package com.example.tests;

import com.example.listeners.TestListener;
import com.example.services.AuthService;
import com.example.models.LoginRequest;
import com.example.models.LoginResponse;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.testng.Assert;
import org.testng.annotations.Listeners;
import org.testng.annotations.Test;

@Listeners(TestListener.class)
public class LoginTest {

    private static final Logger logger = LogManager.getLogger(LoginTest.class);
    private AuthService authService = new AuthService();

    @Test(description = "Verify successful login")
    public void verifySuccessfulLogin() {
        logger.info("Starting verifySuccessfulLogin test");
        
        LoginRequest loginRequest = new LoginRequest("testuser", "password123");
        LoginResponse loginResponse = authService.login(loginRequest).as(LoginResponse.class);
        
        logger.debug("Login Response: " + loginResponse.getToken());
        
        Assert.assertNotNull(loginResponse.getToken(), "Token should not be null");
        Assert.assertEquals(loginResponse.getMessage(), "Login successful");
        
        logger.info("Completed verifySuccessfulLogin test");
    }
}

Explanation

  • Logger Initialization: Each test class initializes its own logger using LogManager.getLogger(ClassName.class).
  • Logging Statements:
    • logger.info(): Provides high-level information about test execution.
    • logger.debug(): Offers detailed insights, useful during debugging.
  • Assertions: Validate the API response, ensuring the presence of a token and verifying the success message.

7. Implementing TestNG Listeners

TestNG listeners allow us to perform actions based on test execution events, such as test start, success, failure, and skip. We’ll implement ITestListener to enhance our logging and reporting capabilities.

Code Example

// src/test/java/com/example/listeners/TestListener.java
package com.example.listeners;

import org.testng.ITestContext;
import org.testng.ITestListener;
import org.testng.ITestResult;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

public class TestListener implements ITestListener {

    private static final Logger logger = LogManager.getLogger(TestListener.class);

    @Override
    public void onStart(ITestContext context) {
        logger.info("=== Test Suite Started ===");
    }

    @Override
    public void onFinish(ITestContext context) {
        logger.info("=== Test Suite Finished ===");
    }

    @Override
    public void onTestStart(ITestResult result) {
        logger.info(">>> Test Started: " + result.getName());
    }

    @Override
    public void onTestSuccess(ITestResult result) {
        logger.info(">>> Test Passed: " + result.getName());
    }

    @Override
    public void onTestFailure(ITestResult result) {
        logger.error(">>> Test Failed: " + result.getName());
        logger.error(">>> Reason: " + result.getThrowable());
    }

    @Override
    public void onTestSkipped(ITestResult result) {
        logger.warn(">>> Test Skipped: " + result.getName());
    }
}

Explanation

  • ITestListener Implementation: Allows interception of test events to perform custom actions.
  • Logging Events:
    • onStart: Logs the commencement of the entire test suite.
    • onFinish: Logs the completion of the test suite.
    • onTestStart: Logs the start of an individual test.
    • onTestSuccess: Logs the successful completion of a test.
    • onTestFailure: Logs failed tests along with the exception details.
    • onTestSkipped: Logs skipped tests.
  • Enhancement: This listener provides real-time feedback in logs, making it easier to trace test executions and failures.

Attaching Listener to Test Classes

Use the @Listeners annotation in your test classes to activate the listener.

// src/test/java/com/example/tests/LoginTest.java
package com.example.tests;

import com.example.listeners.TestListener;
import com.example.services.AuthService;
import com.example.models.LoginRequest;
import com.example.models.LoginResponse;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.testng.Assert;
import org.testng.annotations.Listeners;
import org.testng.annotations.Test;

@Listeners(TestListener.class)
public class LoginTest {
    // ... (same as before)
}

8. Using Filters in RestAssured

Filters in RestAssured allow you to intercept requests and responses, enabling actions like logging, modifying requests, or validating responses before they reach the server or your test assertions.

Implementing a Logging Filter

We’ll create a custom filter to log detailed request and response information.

Code Example

// src/test/java/com/example/filters/LoggingFilter.java
package com.example.filters;

import io.restassured.filter.Filter;
import io.restassured.filter.FilterContext;
import io.restassured.response.Response;
import io.restassured.specification.FilterableRequestSpecification;
import io.restassured.specification.FilterableResponseSpecification;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

public class LoggingFilter implements Filter {

    private static final Logger logger = LogManager.getLogger(LoggingFilter.class);

    @Override
    public Response filter(FilterableRequestSpecification requestSpec, 
                           FilterableResponseSpecification responseSpec, 
                           FilterContext ctx) {
        logRequest(requestSpec);
        Response response = ctx.next(requestSpec, responseSpec);
        logResponse(response);
        return response;
    }

    private void logRequest(FilterableRequestSpecification requestSpec) {
        logger.info("=== Request ===");
        logger.info("URI: " + requestSpec.getURI());
        logger.info("Method: " + requestSpec.getMethod());
        logger.info("Headers: " + redactSensitiveHeaders(requestSpec.getHeaders()));
        if (requestSpec.getBody() != null) {
            logger.info("Body: " + redactSensitiveBody(requestSpec.getBody()));
        }
    }

    private void logResponse(Response response) {
        logger.info("=== Response ===");
        logger.info("Status Code: " + response.getStatusCode());
        logger.info("Headers: " + response.getHeaders());
        logger.info("Body: " + response.getBody().asPrettyString());
    }

    private String redactSensitiveHeaders(io.restassured.http.Headers headers) {
        return headers.asList().stream()
                .map(header -> header.getName().equalsIgnoreCase("Authorization") ? 
                        header.getName() + ": *****" : header.toString())
                .reduce("", (acc, header) -> acc + header + "\n");
    }

    private String redactSensitiveBody(String body) {
        // Implement redaction logic, e.g., replace passwords with asterisks
        return body.replaceAll("\"password\":\"(.*?)\"", "\"password\":\"*****\"");
    }
}

Explanation

  • Filter Interface: The Filter interface requires implementing the filter method, which intercepts both requests and responses.
  • Logging Mechanism:
    • Request Logging: Captures URI, HTTP method, headers, and body of the outgoing request.
    • Response Logging: Captures status code, headers, and body of the incoming response.
  • Redaction: Masks sensitive information like the Authorization header and passwords in the request body to ensure security.
  • Integration: By attaching this filter to the RestAssured configuration, every API call made through the framework will be logged in detail without exposing sensitive data.

Attaching the Logging Filter

Modify the APIBase class to include the custom LoggingFilter.

// src/test/java/com/example/base/APIBase.java
package com.example.base;

import com.example.filters.LoggingFilter;
import io.restassured.builder.RequestSpecBuilder;
import io.restassured.specification.RequestSpecification;
import io.restassured.RestAssured;
import io.restassured.response.Response;

public abstract class APIBase {

    protected static RequestSpecification requestSpec;

    static {
        // Initialize Request Specification
        requestSpec = new RequestSpecBuilder()
                .setBaseUri("https://api.example.com")
                .addHeader("Content-Type", "application/json")
                .addFilter(new LoggingFilter()) // Adding the custom LoggingFilter
                .build();
    }

    protected Response get(String endpoint) {
        return RestAssured.given()
                .spec(requestSpec)
                .get(endpoint);
    }

    protected Response post(String endpoint, Object body) {
        return RestAssured.given()
                .spec(requestSpec)
                .body(body)
                .post(endpoint);
    }

    protected Response put(String endpoint, Object body) {
        return RestAssured.given()
                .spec(requestSpec)
                .body(body)
                .put(endpoint);
    }

    protected Response delete(String endpoint) {
        return RestAssured.given()
                .spec(requestSpec)
                .delete(endpoint);
    }
}

9. Configuring Maven Surefire Plugin

The Maven Surefire Plugin is responsible for executing your TestNG tests during the Maven build lifecycle. We’ll configure it to accept parameters for test suites, enabling flexibility in test execution.

Configuring pom.xml

Update the maven-surefire-plugin configuration in your pom.xml:

<!-- Maven Surefire Plugin Configuration -->
<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-surefire-plugin</artifactId>
    <version>3.0.0-M7</version>
    <configuration>
        <suiteXmlFiles>
            <suiteXmlFile>${suiteXmlFile}</suiteXmlFile>
        </suiteXmlFiles>
    </configuration>
</plugin>

Explanation

  • suiteXmlFile Parameter: Allows specifying which TestNG suite XML file to execute. This provides flexibility to run different test suites (e.g., smoke, regression) without altering the plugin configuration.

Creating TestNG Suite XML

Create a testng.xml file to define your test suites.

<!-- testng.xml -->
<!DOCTYPE suite SYSTEM "https://testng.org/testng-1.0.dtd" >
<suite name="API Test Suite">
    <test name="Authentication Tests">
        <classes>
            <class name="com.example.tests.LoginTest"/>
            <class name="com.example.tests.LogoutTest"/>
        </classes>
    </test>
</suite>

Running Tests via Command Line

Execute tests by specifying the suite XML file.

mvn clean test -DsuiteXmlFile=testng.xml

Explanation

  • Clean: Cleans the project by removing previous build artifacts.
  • Test: Triggers the test phase.
  • -DsuiteXmlFile: Passes the suite XML file as a system property to Maven, directing Surefire to execute the specified tests.

10. Integrating with GitHub Actions

Automating your test executions ensures that your APIs are continuously validated with every code change. GitHub Actions provides a seamless way to integrate CI/CD pipelines within your GitHub repositories.

Setting Up GitHub Actions Workflow

Create Workflow File:

  • In your GitHub repository, navigate to .github/workflows/ and create a file named ci.yml.
  • Define the Workflow:
# .github/workflows/ci.yml
name: API Test Automation

on:
  push:
    branches: [ main ]
  schedule:
    - cron: '0 18 * * *' # Runs daily at 18:00 UTC (23:30 IST)

jobs:
  build:
    runs-on: ubuntu-latest

    steps:
    - name: Checkout Repository
      uses: actions/checkout@v3

    - name: Set up JDK 11
      uses: actions/setup-java@v3
      with:
        java-version: '11'
        distribution: 'adopt'

    - name: Execute Tests
      run: mvn clean test -DsuiteXmlFile=testng.xml

    - name: Upload Test Logs
      uses: actions/upload-artifact@v3
      with:
        name: test-logs
        path: logs/test.log

    - name: Publish TestNG Reports
      uses: actions/upload-artifact@v3
      with:
        name: test-reports
        path: target/surefire-reports/testng-results.xml

Explanation

  • Triggers:
    • Push: Runs the workflow on every push to the main branch.
    • Schedule: Executes the workflow daily at 18:00 UTC, equivalent to 23:30 IST.
  • Jobs:
    • Checkout Repository: Clones your repository into the runner.
    • Set up JDK 11: Installs Java 11 on the runner.
    • Execute Tests: Runs your TestNG tests using Maven.
    • Upload Test Logs: Archives the generated logs (logs/test.log) as build artifacts.
    • Publish TestNG Reports: Archives TestNG result reports for review.

Monitoring Workflow Runs

After committing the ci.yml file, navigate to the Actions tab in your GitHub repository to monitor workflow runs. You can view logs, download artifacts, and analyze test results directly from the GitHub interface.

11. Real-World API Testing Scenarios

Understanding how to apply your API automation framework to real-world scenarios is crucial for effective testing. Below are some practical test cases that demonstrate how to utilize RestAssured in authenticating users, managing user profiles, and handling typical API operations.

• User Registration API Tests

Objective:

Ensure that the user registration API functions correctly under various conditions.

Test Case 1: Successful User Registration

Description: Verify that a new user can register successfully with valid input data.

Steps:

  1. Prepare the Registration Request:
    • Create a UserRegistrationRequest object with valid username, email, and password.
  2. Send the Registration Request:
    • Use the AuthService to send a POST request to the /auth/register endpoint.
  3. Validate the Response:
    • Assert that the response status code is 201 Created.
    • Verify that the response body contains a success message and a unique userId.

Code Example

// src/test/java/com/example/tests/UserRegistrationTest.java
package com.example.tests;

import com.example.listeners.TestListener;
import com.example.services.AuthService;
import com.example.models.UserRegistrationRequest;
import com.example.models.UserRegistrationResponse;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.testng.Assert;
import org.testng.annotations.Listeners;
import org.testng.annotations.Test;

@Listeners(TestListener.class)
public class UserRegistrationTest {

    private static final Logger logger = LogManager.getLogger(UserRegistrationTest.class);
    private AuthService authService = new AuthService();

    @Test(description = "Verify successful user registration")
    public void verifySuccessfulUserRegistration() {
        logger.info("Starting verifySuccessfulUserRegistration test");

        UserRegistrationRequest registrationRequest = new UserRegistrationRequest("newuser", "newuser@example.com", "SecurePass123!");
        UserRegistrationResponse registrationResponse = authService.register(registrationRequest).as(UserRegistrationResponse.class);

        logger.debug("Registration Response: " + registrationResponse.getUserId());

        Assert.assertEquals(registrationResponse.getStatusCode(), 201, "Status code should be 201 Created");
        Assert.assertNotNull(registrationResponse.getUserId(), "User ID should not be null");
        Assert.assertEquals(registrationResponse.getMessage(), "Registration successful");

        logger.info("Completed verifySuccessfulUserRegistration test");
    }
}

Explanation

  • UserRegistrationRequest & UserRegistrationResponse: POJOs representing the registration request and response.
  • AuthService.register(): Sends a POST request to the registration endpoint.
  • Assertions:
    • Status Code: Ensures the API returns 201 Created for successful registrations.
    • User ID: Validates that a unique userId is generated.
    • Message: Confirms the presence of a success message.

Test Case 2: Registration with Missing Fields

Description: Verify that the API returns appropriate errors when required fields are missing.

Steps:

  1. Prepare the Incomplete Registration Request:
    • Create a UserRegistrationRequest object without the email field.
  2. Send the Registration Request:
    • Use the AuthService to send a POST request to the /auth/register endpoint.
  3. Validate the Response:
    • Assert that the response status code is 400 Bad Request.
    • Verify that the response body contains an error message indicating the missing email field.

Code Example

@Test(description = "Verify registration fails with missing email")
public void verifyRegistrationWithMissingEmail() {
    logger.info("Starting verifyRegistrationWithMissingEmail test");

    UserRegistrationRequest registrationRequest = new UserRegistrationRequest("incompleteUser", null, "SecurePass123!");
    UserRegistrationResponse registrationResponse = authService.register(registrationRequest).as(UserRegistrationResponse.class);

    logger.debug("Registration Response: " + registrationResponse.getMessage());

    Assert.assertEquals(registrationResponse.getStatusCode(), 400, "Status code should be 400 Bad Request");
    Assert.assertEquals(registrationResponse.getMessage(), "Email is required");

    logger.info("Completed verifyRegistrationWithMissingEmail test");
}

Explanation

  • Missing Email Field: The email field is set to null to simulate missing input.
  • Assertions:
    • Status Code: Ensures the API returns 400 Bad Request for invalid input.
    • Error Message: Confirms that the error message specifies the missing email field.

• User Authentication API Tests

Objective:

Ensure that the user authentication API functions correctly for both valid and invalid login attempts.

Test Case 1: Successful User Login

Description: Verify that a registered user can log in successfully with valid credentials.

Steps:

  1. Prepare the Login Request:
    • Create a LoginRequest object with valid username and password.
  2. Send the Login Request:
    • Use the AuthService to send a POST request to the /auth/login endpoint.
  3. Validate the Response:
    • Assert that the response status code is 200 OK.
    • Verify that the response body contains a valid token and userId.

Code Example

// src/test/java/com/example/tests/UserAuthenticationTest.java
package com.example.tests;

import com.example.listeners.TestListener;
import com.example.services.AuthService;
import com.example.models.LoginRequest;
import com.example.models.LoginResponse;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.testng.Assert;
import org.testng.annotations.Listeners;
import org.testng.annotations.Test;

@Listeners(TestListener.class)
public class UserAuthenticationTest {

    private static final Logger logger = LogManager.getLogger(UserAuthenticationTest.class);
    private AuthService authService = new AuthService();

    @Test(description = "Verify successful user login")
    public void verifySuccessfulUserLogin() {
        logger.info("Starting verifySuccessfulUserLogin test");

        LoginRequest loginRequest = new LoginRequest("existinguser", "ValidPass123!");
        LoginResponse loginResponse = authService.login(loginRequest).as(LoginResponse.class);

        logger.debug("Login Response Token: " + loginResponse.getToken());

        Assert.assertEquals(loginResponse.getStatusCode(), 200, "Status code should be 200 OK");
        Assert.assertNotNull(loginResponse.getToken(), "Token should not be null");
        Assert.assertEquals(loginResponse.getMessage(), "Login successful");

        logger.info("Completed verifySuccessfulUserLogin test");
    }
}

Explanation

  • LoginRequest & LoginResponse: POJOs representing the login request and response.
  • AuthService.login(): Sends a POST request to the login endpoint.
  • Assertions:
    • Status Code: Ensures the API returns 200 OK for successful logins.
    • Token: Validates that a token is provided upon successful authentication.
    • Message: Confirms the presence of a success message.

Test Case 2: Login with Invalid Credentials

Description: Verify that the API returns appropriate errors when login credentials are invalid.

Steps:

  1. Prepare the Invalid Login Request:
    • Create a LoginRequest object with an incorrect password.
  2. Send the Login Request:
    • Use the AuthService to send a POST request to the /auth/login endpoint.
  3. Validate the Response:
    • Assert that the response status code is 401 Unauthorized.
    • Verify that the response body contains an error message indicating invalid credentials.

Code Example

@Test(description = "Verify login fails with invalid credentials")
public void verifyLoginWithInvalidCredentials() {
    logger.info("Starting verifyLoginWithInvalidCredentials test");

    LoginRequest loginRequest = new LoginRequest("existinguser", "WrongPass!");
    LoginResponse loginResponse = authService.login(loginRequest).as(LoginResponse.class);

    logger.debug("Login Response Message: " + loginResponse.getMessage());

    Assert.assertEquals(loginResponse.getStatusCode(), 401, "Status code should be 401 Unauthorized");
    Assert.assertEquals(loginResponse.getMessage(), "Invalid username or password");

    logger.info("Completed verifyLoginWithInvalidCredentials test");
}

Explanation

  • Invalid Password: The password field is intentionally incorrect to simulate failed authentication.
  • Assertions:
    • Status Code: Ensures the API returns 401 Unauthorized for invalid credentials.
    • Error Message: Confirms that the error message specifies invalid username or password.

• Fetching and Updating User Profile API Tests

Objective:

Ensure that users can retrieve and update their profiles correctly, with proper authorization.

Test Case 1: Fetch User Profile with Valid Token

Description: Verify that a user can retrieve their profile information when providing a valid authentication token.

Steps:

  1. Authenticate and Obtain Token:
    • Use the AuthService to log in and retrieve a valid token.
  2. Send the Profile Fetch Request:
    • Use the UserProfileService to send a GET request to the /user/profile endpoint with the Authorization header.
  3. Validate the Response:
    • Assert that the response status code is 200 OK.
    • Verify that the response body contains accurate user information.

Code Example

// src/test/java/com/example/tests/UserProfileTest.java
package com.example.tests;

import com.example.listeners.TestListener;
import com.example.services.AuthService;
import com.example.services.UserProfileService;
import com.example.models.LoginRequest;
import com.example.models.LoginResponse;
import com.example.models.UserProfileResponse;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.testng.Assert;
import org.testng.annotations.Listeners;
import org.testng.annotations.Test;

@Listeners(TestListener.class)
public class UserProfileTest {

    private static final Logger logger = LogManager.getLogger(UserProfileTest.class);
    private AuthService authService = new AuthService();
    private UserProfileService userProfileService = new UserProfileService();

    @Test(description = "Verify fetching user profile with valid token")
    public void verifyFetchingUserProfile() {
        logger.info("Starting verifyFetchingUserProfile test");

        // Authenticate and get token
        LoginRequest loginRequest = new LoginRequest("existinguser", "ValidPass123!");
        LoginResponse loginResponse = authService.login(loginRequest).as(LoginResponse.class);
        String token = loginResponse.getToken();

        // Fetch user profile
        UserProfileResponse profileResponse = userProfileService.getUserProfile(token).as(UserProfileResponse.class);

        logger.debug("User Profile: " + profileResponse.getUsername());

        Assert.assertEquals(profileResponse.getStatusCode(), 200, "Status code should be 200 OK");
        Assert.assertEquals(profileResponse.getUsername(), "existinguser");
        Assert.assertNotNull(profileResponse.getEmail(), "Email should not be null");

        logger.info("Completed verifyFetchingUserProfile test");
    }
}

Explanation

  • UserProfileService: Handles API calls related to user profiles.
  • Authentication: Logs in to obtain a valid token required for authorized requests.
  • Assertions:
    • Status Code: Ensures the API returns 200 OK when fetching profiles with a valid token.
    • User Information: Validates that the returned profile information matches the expected data.

Test Case 2: Update User Profile with Valid Data

Description: Verify that a user can update their profile information with valid input data.

Steps:

  1. Authenticate and Obtain Token:
    • Use the AuthService to log in and retrieve a valid token.
  2. Prepare the Profile Update Request:
    • Create a UserProfileUpdateRequest object with updated email and phoneNumber.
  3. Send the Profile Update Request:
    • Use the UserProfileService to send a PUT request to the /user/profile endpoint with the Authorization header.
  4. Validate the Response:
    • Assert that the response status code is 200 OK.
    • Verify that the response body contains the updated user information.

Code Example

@Test(description = "Verify updating user profile with valid data")
public void verifyUpdatingUserProfile() {
    logger.info("Starting verifyUpdatingUserProfile test");

    // Authenticate and get token
    LoginRequest loginRequest = new LoginRequest("existinguser", "ValidPass123!");
    LoginResponse loginResponse = authService.login(loginRequest).as(LoginResponse.class);
    String token = loginResponse.getToken();

    // Prepare profile update request
    UserProfileUpdateRequest updateRequest = new UserProfileUpdateRequest("existinguser", "newemail@example.com", "1234567890");

    // Update user profile
    UserProfileResponse updateResponse = userProfileService.updateUserProfile(token, updateRequest).as(UserProfileResponse.class);

    logger.debug("Updated Email: " + updateResponse.getEmail());

    Assert.assertEquals(updateResponse.getStatusCode(), 200, "Status code should be 200 OK");
    Assert.assertEquals(updateResponse.getEmail(), "newemail@example.com");
    Assert.assertEquals(updateResponse.getPhoneNumber(), "1234567890");

    logger.info("Completed verifyUpdatingUserProfile test");
}

Explanation

  • UserProfileUpdateRequest: POJO representing the profile update payload.
  • UserProfileService.updateUserProfile(): Sends a PUT request to update user profile information.
  • Assertions:
    • Status Code: Ensures the API returns 200 OK for successful updates.
    • Updated Information: Confirms that the email and phone number have been updated as expected.

12. Additional Enhancements

To further strengthen your API automation framework, consider implementing the following enhancements:

• Masking Sensitive Data

Protect sensitive information like authentication tokens, passwords, and personal data by masking them in logs and reports.

Implementation Steps:

Modify the Logging Filter:

Update the LoggingFilter to redact sensitive headers or fields.

// src/test/java/com/example/filters/LoggingFilter.java
package com.example.filters;

import io.restassured.filter.Filter;
import io.restassured.filter.FilterContext;
import io.restassured.response.Response;
import io.restassured.specification.FilterableRequestSpecification;
import io.restassured.specification.FilterableResponseSpecification;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

public class LoggingFilter implements Filter {

    private static final Logger logger = LogManager.getLogger(LoggingFilter.class);

    @Override
    public Response filter(FilterableRequestSpecification requestSpec, 
                           FilterableResponseSpecification responseSpec, 
                           FilterContext ctx) {
        logRequest(requestSpec);
        Response response = ctx.next(requestSpec, responseSpec);
        logResponse(response);
        return response;
    }

    private void logRequest(FilterableRequestSpecification requestSpec) {
        logger.info("=== Request ===");
        logger.info("URI: " + requestSpec.getURI());
        logger.info("Method: " + requestSpec.getMethod());
        logger.info("Headers: " + redactSensitiveHeaders(requestSpec.getHeaders()));
        if (requestSpec.getBody() != null) {
            logger.info("Body: " + redactSensitiveBody(requestSpec.getBody()));
        }
    }

    private void logResponse(Response response) {
        logger.info("=== Response ===");
        logger.info("Status Code: " + response.getStatusCode());
        logger.info("Headers: " + response.getHeaders());
        logger.info("Body: " + response.getBody().asPrettyString());
    }

    private String redactSensitiveHeaders(io.restassured.http.Headers headers) {
        return headers.asList().stream()
                .map(header -> header.getName().equalsIgnoreCase("Authorization") ? 
                        header.getName() + ": *****" : header.toString())
                .reduce("", (acc, header) -> acc + header + "\n");
    }

    private String redactSensitiveBody(String body) {
        // Implement redaction logic, e.g., replace passwords with asterisks
        return body.replaceAll("\"password\":\"(.*?)\"", "\"password\":\"*****\"");
    }
}

Explanation:

  • Redact Headers: Replace the value of sensitive headers like Authorization with masked characters.
  • Redact Body: Obscure sensitive fields within the request body, such as password.

• JDBC Connectivity

Integrate database interactions to fetch test data or validate API responses against database records.

Implementation Steps:

Add JDBC Dependency:
<!-- Add in pom.xml -->
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.30</version>
    <scope>test</scope>
</dependency>
Create Database Utility Class:
// src/test/java/com/example/utils/DatabaseUtil.java
package com.example.utils;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.Statement;

public class DatabaseUtil {

    private static final String DB_URL = "jdbc:mysql://localhost:3306/api_test_db";
    private static final String USER = "test_user";
    private static final String PASS = "password123";

    public static ResultSet executeQuery(String query) {
        try {
            Connection conn = DriverManager.getConnection(DB_URL, USER, PASS);
            Statement stmt = conn.createStatement();
            return stmt.executeQuery(query);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }
}
Using Database Data in Tests:
// src/test/java/com/example/tests/LoginTest.java
package com.example.tests;

import com.example.listeners.TestListener;
import com.example.services.AuthService;
import com.example.models.LoginRequest;
import com.example.models.LoginResponse;
import com.example.utils.DatabaseUtil;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.testng.Assert;
import org.testng.annotations.Listeners;
import org.testng.annotations.Test;

import java.sql.ResultSet;

@Listeners(TestListener.class)
public class LoginTest {

    private static final Logger logger = LogManager.getLogger(LoginTest.class);
    private AuthService authService = new AuthService();

    @Test(description = "Verify successful login with database credentials")
    public void verifyLoginWithDBCredentials() {
        logger.info("Starting verifyLoginWithDBCredentials test");

        // Fetch credentials from the database
        String query = "SELECT username, password FROM users WHERE user_id = 1";
        ResultSet rs = DatabaseUtil.executeQuery(query);
        String username = "";
        String password = "";
        try {
            if (rs.next()) {
                username = rs.getString("username");
                password = rs.getString("password");
            }
        } catch (Exception e) {
            logger.error("Database query failed: " + e.getMessage());
        }

        LoginRequest loginRequest = new LoginRequest(username, password);
        LoginResponse loginResponse = authService.login(loginRequest).as(LoginResponse.class);

        logger.debug("Login Response: " + loginResponse.getToken());

        Assert.assertNotNull(loginResponse.getToken(), "Token should not be null");
        Assert.assertEquals(loginResponse.getMessage(), "Login successful");

        logger.info("Completed verifyLoginWithDBCredentials test");
    }
}

Explanation:

  • DatabaseUtil: Facilitates executing SQL queries and retrieving results.
  • Test Integration: Fetches user credentials directly from the database, ensuring that tests use accurate and up-to-date data.
  • Error Handling: Logs any issues encountered during database interactions.

11. Real-World API Testing Scenarios

In this section, we’ll explore real-world API testing scenarios that demonstrate how to apply the RestAssured framework effectively. These scenarios include testing user registration, authentication, and profile management APIs with both positive and negative test cases.

• User Registration API Tests

Objective:

Ensure that the user registration API functions correctly under various conditions.

Test Case 1: Successful User Registration

Description: Verify that a new user can register successfully with valid input data.

Steps:

  1. Prepare the Registration Request:
    • Create a UserRegistrationRequest object with valid username, email, and password.
  2. Send the Registration Request:
    • Use the AuthService to send a POST request to the /auth/register endpoint.
  3. Validate the Response:
    • Assert that the response status code is 201 Created.
    • Verify that the response body contains a success message and a unique userId.

Code Example

// src/test/java/com/example/tests/UserRegistrationTest.java
package com.example.tests;

import com.example.listeners.TestListener;
import com.example.services.AuthService;
import com.example.models.UserRegistrationRequest;
import com.example.models.UserRegistrationResponse;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.testng.Assert;
import org.testng.annotations.Listeners;
import org.testng.annotations.Test;

@Listeners(TestListener.class)
public class UserRegistrationTest {

    private static final Logger logger = LogManager.getLogger(UserRegistrationTest.class);
    private AuthService authService = new AuthService();

    @Test(description = "Verify successful user registration")
    public void verifySuccessfulUserRegistration() {
        logger.info("Starting verifySuccessfulUserRegistration test");

        UserRegistrationRequest registrationRequest = new UserRegistrationRequest("newuser", "newuser@example.com", "SecurePass123!");
        UserRegistrationResponse registrationResponse = authService.register(registrationRequest).as(UserRegistrationResponse.class);

        logger.debug("Registration Response: " + registrationResponse.getUserId());

        Assert.assertEquals(registrationResponse.getStatusCode(), 201, "Status code should be 201 Created");
        Assert.assertNotNull(registrationResponse.getUserId(), "User ID should not be null");
        Assert.assertEquals(registrationResponse.getMessage(), "Registration successful");

        logger.info("Completed verifySuccessfulUserRegistration test");
    }
}

Explanation

  • UserRegistrationRequest & UserRegistrationResponse: POJOs representing the registration request and response.
  • AuthService.register(): Sends a POST request to the registration endpoint.
  • Assertions:
    • Status Code: Ensures the API returns 201 Created for successful registrations.
    • User ID: Validates that a unique userId is generated.
    • Message: Confirms the presence of a success message.

Test Case 2: Registration with Missing Fields

Description: Verify that the API returns appropriate errors when required fields are missing.

Steps:

  1. Prepare the Incomplete Registration Request:
    • Create a UserRegistrationRequest object without the email field.
  2. Send the Registration Request:
    • Use the AuthService to send a POST request to the /auth/register endpoint.
  3. Validate the Response:
    • Assert that the response status code is 400 Bad Request.
    • Verify that the response body contains an error message indicating the missing email field.

Code Example

@Test(description = "Verify registration fails with missing email")
public void verifyRegistrationWithMissingEmail() {
    logger.info("Starting verifyRegistrationWithMissingEmail test");

    UserRegistrationRequest registrationRequest = new UserRegistrationRequest("incompleteUser", null, "SecurePass123!");
    UserRegistrationResponse registrationResponse = authService.register(registrationRequest).as(UserRegistrationResponse.class);

    logger.debug("Registration Response: " + registrationResponse.getMessage());

    Assert.assertEquals(registrationResponse.getStatusCode(), 400, "Status code should be 400 Bad Request");
    Assert.assertEquals(registrationResponse.getMessage(), "Email is required");

    logger.info("Completed verifyRegistrationWithMissingEmail test");
}

Explanation

  • Missing Email Field: The email field is set to null to simulate missing input.
  • Assertions:
    • Status Code: Ensures the API returns 400 Bad Request for invalid input.
    • Error Message: Confirms that the error message specifies the missing email field.

• User Authentication API Tests

Objective:

Ensure that the user authentication API functions correctly for both valid and invalid login attempts.

Test Case 1: Successful User Login

Description: Verify that a registered user can log in successfully with valid credentials.

Steps:

  1. Prepare the Login Request:
    • Create a LoginRequest object with valid username and password.
  2. Send the Login Request:
    • Use the AuthService to send a POST request to the /auth/login endpoint.
  3. Validate the Response:
    • Assert that the response status code is 200 OK.
    • Verify that the response body contains a valid token and userId.

Code Example

// src/test/java/com/example/tests/UserAuthenticationTest.java
package com.example.tests;

import com.example.listeners.TestListener;
import com.example.services.AuthService;
import com.example.models.LoginRequest;
import com.example.models.LoginResponse;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.testng.Assert;
import org.testng.annotations.Listeners;
import org.testng.annotations.Test;

@Listeners(TestListener.class)
public class UserAuthenticationTest {

    private static final Logger logger = LogManager.getLogger(UserAuthenticationTest.class);
    private AuthService authService = new AuthService();

    @Test(description = "Verify successful user login")
    public void verifySuccessfulUserLogin() {
        logger.info("Starting verifySuccessfulUserLogin test");

        LoginRequest loginRequest = new LoginRequest("existinguser", "ValidPass123!");
        LoginResponse loginResponse = authService.login(loginRequest).as(LoginResponse.class);

        logger.debug("Login Response Token: " + loginResponse.getToken());

        Assert.assertEquals(loginResponse.getStatusCode(), 200, "Status code should be 200 OK");
        Assert.assertNotNull(loginResponse.getToken(), "Token should not be null");
        Assert.assertEquals(loginResponse.getMessage(), "Login successful");

        logger.info("Completed verifySuccessfulUserLogin test");
    }
}

Explanation

  • LoginRequest & LoginResponse: POJOs representing the login request and response.
  • AuthService.login(): Sends a POST request to the login endpoint.
  • Assertions:
    • Status Code: Ensures the API returns 200 OK for successful logins.
    • Token: Validates that a token is provided upon successful authentication.
    • Message: Confirms the presence of a success message.

Test Case 2: Login with Invalid Credentials

Description: Verify that the API returns appropriate errors when login credentials are invalid.

Steps:

  1. Prepare the Invalid Login Request:
    • Create a LoginRequest object with an incorrect password.
  2. Send the Login Request:
    • Use the AuthService to send a POST request to the /auth/login endpoint.
  3. Validate the Response:
    • Assert that the response status code is 401 Unauthorized.
    • Verify that the response body contains an error message indicating invalid credentials.

Code Example

@Test(description = "Verify login fails with invalid credentials")
public void verifyLoginWithInvalidCredentials() {
    logger.info("Starting verifyLoginWithInvalidCredentials test");

    LoginRequest loginRequest = new LoginRequest("existinguser", "WrongPass!");
    LoginResponse loginResponse = authService.login(loginRequest).as(LoginResponse.class);

    logger.debug("Login Response Message: " + loginResponse.getMessage());

    Assert.assertEquals(loginResponse.getStatusCode(), 401, "Status code should be 401 Unauthorized");
    Assert.assertEquals(loginResponse.getMessage(), "Invalid username or password");

    logger.info("Completed verifyLoginWithInvalidCredentials test");
}

Explanation

  • Invalid Password: The password field is intentionally incorrect to simulate failed authentication.
  • Assertions:
    • Status Code: Ensures the API returns 401 Unauthorized for invalid credentials.
    • Error Message: Confirms that the error message specifies invalid username or password.

• Fetching and Updating User Profile API Tests

Objective:

Ensure that users can retrieve and update their profiles correctly, with proper authorization.

Test Case 1: Fetch User Profile with Valid Token

Description: Verify that a user can retrieve their profile information when providing a valid authentication token.

Steps:

  1. Authenticate and Obtain Token:
    • Use the AuthService to log in and retrieve a valid token.
  2. Send the Profile Fetch Request:
    • Use the UserProfileService to send a GET request to the /user/profile endpoint with the Authorization header.
  3. Validate the Response:
    • Assert that the response status code is 200 OK.
    • Verify that the response body contains accurate user information.

Code Example

// src/test/java/com/example/tests/UserProfileTest.java
package com.example.tests;

import com.example.listeners.TestListener;
import com.example.services.AuthService;
import com.example.services.UserProfileService;
import com.example.models.LoginRequest;
import com.example.models.LoginResponse;
import com.example.models.UserProfileResponse;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.testng.Assert;
import org.testng.annotations.Listeners;
import org.testng.annotations.Test;

@Listeners(TestListener.class)
public class UserProfileTest {

    private static final Logger logger = LogManager.getLogger(UserProfileTest.class);
    private AuthService authService = new AuthService();
    private UserProfileService userProfileService = new UserProfileService();

    @Test(description = "Verify fetching user profile with valid token")
    public void verifyFetchingUserProfile() {
        logger.info("Starting verifyFetchingUserProfile test");

        // Authenticate and get token
        LoginRequest loginRequest = new LoginRequest("existinguser", "ValidPass123!");
        LoginResponse loginResponse = authService.login(loginRequest).as(LoginResponse.class);
        String token = loginResponse.getToken();

        // Fetch user profile
        UserProfileResponse profileResponse = userProfileService.getUserProfile(token).as(UserProfileResponse.class);

        logger.debug("User Profile: " + profileResponse.getUsername());

        Assert.assertEquals(profileResponse.getStatusCode(), 200, "Status code should be 200 OK");
        Assert.assertEquals(profileResponse.getUsername(), "existinguser");
        Assert.assertNotNull(profileResponse.getEmail(), "Email should not be null");

        logger.info("Completed verifyFetchingUserProfile test");
    }
}

Explanation

  • UserProfileService: Handles API calls related to user profiles.
  • Authentication: Logs in to obtain a valid token required for authorized requests.
  • Assertions:
    • Status Code: Ensures the API returns 200 OK when fetching profiles with a valid token.
    • User Information: Validates that the returned profile information matches the expected data.

Test Case 2: Update User Profile with Valid Data

Description: Verify that a user can update their profile information with valid input data.

Steps:

  1. Authenticate and Obtain Token:
    • Use the AuthService to log in and retrieve a valid token.
  2. Prepare the Profile Update Request:
    • Create a UserProfileUpdateRequest object with updated email and phoneNumber.
  3. Send the Profile Update Request:
    • Use the UserProfileService to send a PUT request to the /user/profile endpoint with the Authorization header.
  4. Validate the Response:
    • Assert that the response status code is 200 OK.
    • Verify that the response body contains the updated user information.

Code Example

@Test(description = "Verify updating user profile with valid data")
public void verifyUpdatingUserProfile() {
    logger.info("Starting verifyUpdatingUserProfile test");

    // Authenticate and get token
    LoginRequest loginRequest = new LoginRequest("existinguser", "ValidPass123!");
    LoginResponse loginResponse = authService.login(loginRequest).as(LoginResponse.class);
    String token = loginResponse.getToken();

    // Prepare profile update request
    UserProfileUpdateRequest updateRequest = new UserProfileUpdateRequest("existinguser", "newemail@example.com", "1234567890");

    // Update user profile
    UserProfileResponse updateResponse = userProfileService.updateUserProfile(token, updateRequest).as(UserProfileResponse.class);

    logger.debug("Updated Email: " + updateResponse.getEmail());

    Assert.assertEquals(updateResponse.getStatusCode(), 200, "Status code should be 200 OK");
    Assert.assertEquals(updateResponse.getEmail(), "newemail@example.com");
    Assert.assertEquals(updateResponse.getPhoneNumber(), "1234567890");

    logger.info("Completed verifyUpdatingUserProfile test");
}

Explanation

  • UserProfileUpdateRequest: POJO representing the profile update payload.
  • UserProfileService.updateUserProfile(): Sends a PUT request to update user profile information.
  • Assertions:
    • Status Code: Ensures the API returns 200 OK for successful updates.
    • Updated Information: Confirms that the email and phone number have been updated as expected.

12. Additional Enhancements

To further strengthen your API automation framework, consider implementing the following enhancements:

• Masking Sensitive Data

Protect sensitive information like authentication tokens, passwords, and personal data by masking them in logs and reports.

Implementation Steps:

Modify the Logging Filter:

Update the LoggingFilter to redact sensitive headers or fields.

// src/test/java/com/example/filters/LoggingFilter.java
package com.example.filters;

import io.restassured.filter.Filter;
import io.restassured.filter.FilterContext;
import io.restassured.response.Response;
import io.restassured.specification.FilterableRequestSpecification;
import io.restassured.specification.FilterableResponseSpecification;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

public class LoggingFilter implements Filter {

    private static final Logger logger = LogManager.getLogger(LoggingFilter.class);

    @Override
    public Response filter(FilterableRequestSpecification requestSpec, 
                           FilterableResponseSpecification responseSpec, 
                           FilterContext ctx) {
        logRequest(requestSpec);
        Response response = ctx.next(requestSpec, responseSpec);
        logResponse(response);
        return response;
    }

    private void logRequest(FilterableRequestSpecification requestSpec) {
        logger.info("=== Request ===");
        logger.info("URI: " + requestSpec.getURI());
        logger.info("Method: " + requestSpec.getMethod());
        logger.info("Headers: " + redactSensitiveHeaders(requestSpec.getHeaders()));
        if (requestSpec.getBody() != null) {
            logger.info("Body: " + redactSensitiveBody(requestSpec.getBody()));
        }
    }

    private void logResponse(Response response) {
        logger.info("=== Response ===");
        logger.info("Status Code: " + response.getStatusCode());
        logger.info("Headers: " + response.getHeaders());
        logger.info("Body: " + response.getBody().asPrettyString());
    }

    private String redactSensitiveHeaders(io.restassured.http.Headers headers) {
        return headers.asList().stream()
                .map(header -> header.getName().equalsIgnoreCase("Authorization") ? 
                        header.getName() + ": *****" : header.toString())
                .reduce("", (acc, header) -> acc + header + "\n");
    }

    private String redactSensitiveBody(String body) {
        // Implement redaction logic, e.g., replace passwords with asterisks
        return body.replaceAll("\"password\":\"(.*?)\"", "\"password\":\"*****\"");
    }
}

Explanation:

  • Redact Headers: Replace the value of sensitive headers like Authorization with masked characters.
  • Redact Body: Obscure sensitive fields within the request body, such as password.

• JDBC Connectivity

Integrate database interactions to fetch test data or validate API responses against database records.

Implementation Steps:

Add JDBC Dependency:
<!-- Add in pom.xml -->
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.30</version>
    <scope>test</scope>
</dependency>
Create Database Utility Class:
// src/test/java/com/example/utils/DatabaseUtil.java
package com.example.utils;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.Statement;

public class DatabaseUtil {

    private static final String DB_URL = "jdbc:mysql://localhost:3306/api_test_db";
    private static final String USER = "test_user";
    private static final String PASS = "password123";

    public static ResultSet executeQuery(String query) {
        try {
            Connection conn = DriverManager.getConnection(DB_URL, USER, PASS);
            Statement stmt = conn.createStatement();
            return stmt.executeQuery(query);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }
}
Using Database Data in Tests:
// src/test/java/com/example/tests/LoginTest.java
package com.example.tests;

import com.example.listeners.TestListener;
import com.example.services.AuthService;
import com.example.models.LoginRequest;
import com.example.models.LoginResponse;
import com.example.utils.DatabaseUtil;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.testng.Assert;
import org.testng.annotations.Listeners;
import org.testng.annotations.Test;

import java.sql.ResultSet;

@Listeners(TestListener.class)
public class LoginTest {

    private static final Logger logger = LogManager.getLogger(LoginTest.class);
    private AuthService authService = new AuthService();

    @Test(description = "Verify successful login with database credentials")
    public void verifyLoginWithDBCredentials() {
        logger.info("Starting verifyLoginWithDBCredentials test");

        // Fetch credentials from the database
        String query = "SELECT username, password FROM users WHERE user_id = 1";
        ResultSet rs = DatabaseUtil.executeQuery(query);
        String username = "";
        String password = "";
        try {
            if (rs.next()) {
                username = rs.getString("username");
                password = rs.getString("password");
            }
        } catch (Exception e) {
            logger.error("Database query failed: " + e.getMessage());
        }

        LoginRequest loginRequest = new LoginRequest(username, password);
        LoginResponse loginResponse = authService.login(loginRequest).as(LoginResponse.class);

        logger.debug("Login Response: " + loginResponse.getToken());

        Assert.assertNotNull(loginResponse.getToken(), "Token should not be null");
        Assert.assertEquals(loginResponse.getMessage(), "Login successful");

        logger.info("Completed verifyLoginWithDBCredentials test");
    }
}

Explanation:

  • DatabaseUtil: Facilitates executing SQL queries and retrieving results.
  • Test Integration: Fetches user credentials directly from the database, ensuring that tests use accurate and up-to-date data.
  • Error Handling: Logs any issues encountered during database interactions.

13. Conclusion

Building a robust API automation framework using RestAssured involves several critical components, from setting up the project structure and implementing design patterns to integrating logging, listeners, and CI/CD pipelines. By following this guide, you’ve laid the foundation for efficient and maintainable API testing, ensuring that your backend services perform as expected.

Key Takeaways:

  • Modular Design: Separating concerns through base classes and service classes enhances code reusability.
  • Detailed Logging: Implementing Log4j2 and TestNG listeners provides comprehensive insights into test executions.
  • Automation Integration: Utilizing GitHub Actions automates your testing process, promoting continuous integration.
  • Enhanced Security: Masking sensitive data and integrating database connectivity strengthen your framework’s robustness.
  • Real-World Application: Implementing practical test cases ensures that your framework is applicable to real-world scenarios.