top of page

Welcome
to NumpyNinja Blogs

NumpyNinja: Blogs. Demystifying Tech,

One Blog at a Time.
Millions of views. 

My First API Automation Framework Using RestAssured: What I Learned During an LMS Hackathon

Jun 4
6 min read

Introduction

It was day one of the LMS API Hackathon, and our team had been given a challenge: build a complete API automation framework from scratch using RestAssured, Java, and Cucumber. Before this hackathon, most of my API testing experience was limited to Postman. I could send requests, validate responses, and test APIs manually. Building an automation framework was an entirely different challenge.

Questions like where request logic should live, how different classes should interact, and how to avoid repeating code were things I had never needed to think about before.


As the hackathon progressed, things started clicking. Not just the how, but the why behind each decision. In this blog, I will share the framework we built, some challenges we faced, and the key lessons I learned along the way.



Maven Dependencies

Setting up the project was straightforward; most dependencies were standard and available at mvnrepository.com. Choosing which libraries to include forced us to think about what each one was actually solving:


Core Testing

  • rest-assured: The heart of the framework. Lets us send HTTP requests and chain assertions on the response in a fluent, readable syntax.

  • cucumber-java + cucumber-testng: These work together: cucumber-java maps Gherkin steps to Java methods, while cucumber-testng lets TestNG discover and run the feature files. We chose Cucumber specifically so non-technical stakeholders could read the test scenarios without touching Java code.


Data & Serialization

  • jackson-databind: Converts Java objects (POJOs) to JSON and back. Essential for building request bodies cleanly instead of hardcoding raw JSON strings, which become a maintenance nightmare over time.

  • poi-ooxml: Reads test data from Excel files. We used this for data-driven scenarios where the same test needed to run against many different input combinations stored in a spreadsheet.

  • json-schema-validator: Validates that an API response matches an expected structure, not just an expected value. This catches breaking changes in the API contract early, even if the status code is still 200.


Reporting & Observability

  • allure-cucumber7-jvm: Generates visual HTML reports after test execution, making it easier to review results and identify failures.

  • log4j: Logs every request and response during execution. Our first debugging session without this was painful, so we added it right away.



Framework Structure

Here is the structure we ended up with after a few rounds of reorganizing:

src/test/java
  ├── api.BaseClass       → BaseClass.java
  ├── api.Utilities       → CommonUtility, Excel Reader, LoggerLoad
  ├── api.Request         → GetProgRequest, PostProgRequest, etc.
  ├── api.Payload         → Request body data
  ├── api.Pojo            → Map the response structure for deserialization
  └── api.StepDefinitions → Cucumber step bindings
RestAssured Framework Structure
RestAssured Framework Structure

We organized the project so each layer had one responsibility. Early on, we made the mistake of writing request logic directly inside step definitions. Everything became hard to read and impossible to reuse. Splitting them out into separate classes fixed both problems.



BaseClass

BaseClass holds the common configuration for every API request: base URL, content type, and logging. Before we had this, the same setup block was being copy-pasted into every request class. Once we moved it into BaseClass, the duplication was gone:


public void setBaseRequest() {
             requestSpec = new RequestSpecBuilder()
               .setBaseUri(CommonUtility.endpoints.getString("baseURL"))
               .setContentType(ContentType.JSON)
               .log(LogDetail.ALL)
               .build();
}


CommonUtility Class

CommonUtility stores values that multiple classes need to access, mainly the auth token, program ID, and batch ID:

public static String token;
public static int programId;
public static int batchId;

A small but important note: leaning on static fields works fine for a single-threaded run but causes subtle bugs when tests run in parallel.



Token Handling

Token handling was trickier than expected. We needed a valid auth token for every scenario, but hitting the login API each time added unnecessary overhead. A Cucumber Background step with a guard check solved it:

Feature: Program Module
Background:
    Given Admin generates valid token
// Inside the step definition:
  if (CommonUtility.getToken() == null) {
      LoginPojo requestBody = LoginPayload.buildValidLoginRequest();
      loginReq.sendLoginRequest("POST", "loginEndpt", requestBody, "NoAuth");
  }

The null check means the login API is called only once per run. Without it, the login endpoint fired before every single scenario. It also meant that if the login API was down, every test would fail, not just the ones that actually needed authentication.



Request Classes

We created separate request classes for each operation (GET, POST, PUT, and DELETE) across the Program and Batch modules, along with a dedicated LoginRequest class. Rather than creating a new method for every positive and negative test case, we designed the request methods to accept parameters that controlled variations such as endpoint validity, token validity, and HTTP method:

String endpoint = valid ? getPrograms : "/invalid";
String token = validToken
        ? "Bearer " + CommonUtility.token
        : "Bearer Invalid";

This approach allowed a single method to support multiple scenarios without duplicating HTTP request logic. The Cucumber step definitions simply passed different parameter combinations based on the scenario being executed. As the number of test cases grew, this design kept the framework easier to maintain and reduced the amount of code that needed to change when new scenarios were added.

One of the biggest lessons for us was realizing that reusable request methods are far more valuable than writing separate methods for every test case. A little effort spent designing flexible request classes upfront saved a significant amount of maintenance later.



Feature File (BDD Approach)

We used Scenario Outline with Examples for data-driven testing:

Background:
    Given Admin generates valid token

  Scenario Outline: Get all programs with "<Scenario>"
    When Admin sends "<Method>" request to get all programs with "<EndpointType>" endpoint and "<TokenType>" token
    Then Admin receives <Statuscode> status code

    Examples:
      | Scenario         | Method | EndpointType | TokenType | Statuscode|
      | invalid endpoint | GET    | invalid      | valid     | 404       |
      | invalid method   | POST   | valid        | valid     | 405       |
      | invalid token    | GET    | valid        | invalid   | 401       |
      | valid endpoint   | GET    | valid        | valid     | 200       |

Each example row executes as an independent test case. Adding new scenarios later was just a matter of adding a row. No Java code changes required.

The diagram below shows how a Cucumber scenario moves through the framework during execution.




During execution, a Cucumber scenario triggers a step definition, which calls the appropriate request class. The request class uses BaseClass to build the request specification, sends the API request through RestAssured, and maps the response into POJOs for validation. Utilities such as token management, logging, and test data retrieval support the process throughout the execution flow.



Lessons Learned

The hackathon taught me much more than how to use RestAssured or Cucumber. It changed the way I think about automation framework design and maintainability:

  • Structure matters more than test count.

As more scenarios were added, the framework structure became more important than the number of tests. Separating requests, payloads, utilities, and step definitions made the code easier to maintain and extend.


  • Readable Tests Are Documentation.

Using Gherkin initially felt like extra work, but the feature files quickly became easy-to-read documentation that both technical and non-technical team members could understand.


  • Test Data Matters as Much as Test Code.

One of our hardest-to-find bugs was caused by a trailing space in an Excel file, not the framework itself. It was a reminder that poor test data can cause just as many problems as bad code.


  • Small Design Decisions Pay Off.

Centralizing configuration, reusing tokens, and parameterizing request methods seemed like small improvements at first, but they significantly reduced duplication and maintenance effort as the framework grew.



What I Would Improve Next

  • Extract token management into a dedicated AuthService.

Instead of checking for a null token in the step definition, an AuthService class would manage token lifecycle cleanly and be injected wherever needed.


  • Separate test data entirely from code.

Right now some data lives in Examples tables, some in Excel files, and some is hardcoded. A single external data source or a dedicated test data file would make the framework more predictable.


  • Add richer failure logs.

When a test fails in CI, the current logs require manual cross-referencing with the Allure report. Embedding the request and response directly in the failure message would cut debugging time noticeably.


  • Trim all values at the Excel reader level.

Manually cleaning spreadsheet cells works but doesn’t scale. Adding .trim() to every value coming out of ExcelReader would catch trailing spaces automatically and make the framework resilient to this class of bug entirely.



Conclusion

This was the first time I built something instead of just using it. The framework itself is relatively simple, but the process of designing it, making mistakes, refactoring, and debugging taught me far more than any tutorial could.


Using API testing tools is one thing; building a framework around them is another. The hackathon taught me how to think about reusability, maintainability, and framework design, not just API testing.


RestAssured and Cucumber are tools. Knowing how to structure the system around them is the real skill, and that was the most valuable takeaway from the experience.


 
 

+1 (302) 200-8320

NumPy_Ninja_Logo (1).png

Numpy Ninja Inc. 8 The Grn Ste A Dover, DE 19901

© Copyright 2025 by Numpy Ninja Inc.

  • Twitter
  • LinkedIn
bottom of page