Introduction
In our REST Assured series, we’ve covered topics like Parameterized Tests, REST Assured with JUnit, and Request and Response Logging, building a robust toolkit for API testing. Now, we’ll explore POJO Mapping, a technique to map API responses to Plain Old Java Objects (POJOs) for easier validation and test maintainability. This guide demonstrates how to use REST Assured to deserialize JSON responses into POJOs, with practical examples and best practices. It’s designed for beginners and experienced developers, ensuring you can handle complex API data effectively.
Key Point: POJO Mapping in REST Assured simplifies API response validation by converting JSON or XML data into Java objects, enabling object-oriented assertions.
What is POJO Mapping?
A POJO (Plain Old Java Object) is a simple Java class with private fields, public getters, and setters, without complex dependencies. In REST Assured, POJO mapping involves deserializing API responses (typically JSON) into POJO instances using libraries like Jackson or Gson, which REST Assured supports out of the box. Benefits include:
- Type Safety: Validate response data using Java’s type system.
- Readability: Perform assertions on object properties instead of JSON paths.
- Reusability: Reuse POJOs across multiple tests or projects.
- Complex Data Handling: Easily manage nested objects and collections.
We’ll use https://jsonplaceholder.typicode.com
for examples, mapping responses from endpoints like /posts
and /comments
to POJOs, including nested structures and lists.
Setting Up for POJO Mapping
We’ll set up a Maven project with REST Assured, JUnit 5, and Allure for reporting, consistent with previous posts. Jackson is included via REST Assured’s dependencies for JSON deserialization.
Here’s the pom.xml
, styled with your preferred Blogger format for XML syntax highlighting:
4.0.0
com.example
rest-assured-pojo
1.0-SNAPSHOT
11
2.27.0
1.9.22
io.rest-assured
rest-assured
5.4.0
test
org.junit.jupiter
junit-jupiter
5.10.2
test
org.hamcrest
hamcrest
2.2
test
com.fasterxml.jackson.core
jackson-databind
2.15.2
test
io.qameta.allure
allure-junit5
${allure.version}
test
org.apache.maven.plugins
maven-surefire-plugin
3.2.5
-javaagent:"${settings.localRepository}/org/aspectj/aspectjweaver/${aspectj.version}/aspectjweaver-${aspectj.version}.jar"
org.aspectj
aspectjweaver
${aspectj.version}
io.qameta.allure
allure-maven
2.12.0
${allure.version}
No additional setup is required for https://jsonplaceholder.typicode.com
. We’ll create POJO classes to match the API’s JSON structure.
Implementing POJO Mapping
Let’s explore how to map API responses to POJOs using REST Assured, covering simple objects, nested objects, and collections.
Example 1: Mapping a Single Object
Map a single post response to a POJO.
Create the Post
POJO:
public class Post {
private int id;
private int userId;
private String title;
private String body;
// Default constructor for Jackson
public Post() {}
// Getters and Setters
public int getId() { return id; }
public void setId(int id) { this.id = id; }
public int getUserId() { return userId; }
public void setUserId(int userId) { this.userId = userId; }
public String getTitle() { return title; }
public void setTitle(String title) { this.title = title; }
public String getBody() { return body; }
public void setBody(String body) { this.body = body; }
}
Test code:
import io.qameta.allure.Description;
import io.qameta.allure.Feature;
import io.restassured.RestAssured;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import static io.restassured.RestAssured.*;
import static org.junit.jupiter.api.Assertions.*;
@Feature("POJO Mapping with REST Assured")
public class SingleObjectMappingTest {
@BeforeEach
public void setup() {
RestAssured.baseURI = "https://jsonplaceholder.typicode.com";
}
@Test
@DisplayName("Test mapping a single post to POJO")
@Description("Test mapping a single post response to a POJO")
public void testMapSinglePost() {
Post post = given()
.log().all()
.when()
.get("/posts/1")
.then()
.log().all()
.statusCode(200)
.extract().as(Post.class);
assertEquals(1, post.getId(), "Post ID should be 1");
assertTrue(post.getUserId() > 0, "User ID should be positive");
assertNotNull(post.getTitle(), "Title should not be null");
assertNotNull(post.getBody(), "Body should not be null");
}
}
Explanation:
Post
POJO: Matches the JSON structure of a post (e.g.,{"id":1,"userId":1,"title":"...","body":"..."}
).extract().as(Post.class)
: Deserializes the JSON response into aPost
object using Jackson.- JUnit assertions validate the POJO’s properties.
Important: Ensure POJO field names match JSON keys, or use Jackson annotations (e.g., @JsonProperty
) for mismatches.
Example 2: Mapping Nested Objects
Map a comment response with a nested post reference.
Create the Comment
and PostReference
POJOs:
public class Comment {
private int id;
private String name;
private String email;
private String body;
private PostReference post;
// Default constructor for Jackson
public Comment() {}
// Getters and Setters
public int getId() { return id; }
public void setId(int id) { this.id = id; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public String getEmail() { return email; }
public void setEmail(String email) { this.email = email; }
public String getBody() { return body; }
public void setBody(String body) { this.body = body; }
public PostReference getPost() { return post; }
public void setPost(PostReference post) { this.post = post; }
}
public class PostReference {
private int id;
// Default constructor for Jackson
public PostReference() {}
// Getters and Setters
public int getId() { return id; }
public void setId(int id) { this.id = id; }
}
Test code:
import io.qameta.allure.Description;
import io.qameta.allure.Feature;
import io.restassured.RestAssured;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import static io.restassured.RestAssured.*;
import static org.junit.jupiter.api.Assertions.*;
@Feature("POJO Mapping with REST Assured")
public class NestedObjectMappingTest {
@BeforeEach
public void setup() {
RestAssured.baseURI = "https://jsonplaceholder.typicode.com";
}
@Test
@DisplayName("Test mapping a comment with nested post to POJO")
@Description("Test mapping a comment response with nested post to POJO")
public void testMapCommentWithPost() {
Comment comment = given()
.log().all()
.when()
.get("/comments/1")
.then()
.log().all()
.statusCode(200)
.extract().as(Comment.class);
assertEquals(1, comment.getId(), "Comment ID should be 1");
assertNotNull(comment.getEmail(), "Email should not be null");
assertNotNull(comment.getPost(), "Post reference should not be null");
assertEquals(1, comment.getPost().getId(), "Post ID should be 1");
}
}
Explanation:
Comment
POJO: Includes aPostReference
object to map the nestedpostId
field.- Assertions validate both top-level and nested properties.
- Jackson automatically maps nested JSON fields to corresponding POJO fields.
Example 3: Mapping a List of Objects
Map a list of posts to a collection of POJOs.
Use the Post
POJO from Example 1.
Test code:
import io.qameta.allure.Description;
import io.qameta.allure.Feature;
import io.restassured.RestAssured;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import java.util.List;
import static io.restassured.RestAssured.*;
import static org.junit.jupiter.api.Assertions.*;
@Feature("POJO Mapping with REST Assured")
public class ListMappingTest {
@BeforeEach
public void setup() {
RestAssured.baseURI = "https://jsonplaceholder.typicode.com";
}
@Test
@DisplayName("Test mapping a list of posts to POJOs")
@Description("Test mapping a list of posts to a collection of POJOs")
public void testMapListOfPosts() {
List posts = given()
.log().all()
.when()
.get("/posts")
.then()
.log().all()
.statusCode(200)
.extract().jsonPath().getList("", Post.class);
assertEquals(100, posts.size(), "Should return 100 posts");
assertTrue(posts.stream().allMatch(post -> post.getId() > 0), "All post IDs should be positive");
assertTrue(posts.stream().allMatch(post -> post.getTitle() != null), "All titles should be non-null");
}
}
Explanation:
getList("", Post.class)
: Deserializes the JSON array into aList
.- Assertions use Java streams to validate properties across all posts.
- The empty path
""
indicates the root array in the response.
Pro Tip: Use jsonPath().getList()
for collections, specifying the POJO class to map array elements.
Example 4: Serializing POJO for Requests
Serialize a POJO to JSON for a POST request.
Use the Post
POJO from Example 1.
Test code:
import io.qameta.allure.Description;
import io.qameta.allure.Feature;
import io.restassured.RestAssured;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import static io.restassured.RestAssured.*;
import static org.hamcrest.Matchers.*;
@Feature("POJO Mapping with REST Assured")
public class SerializePOJOTest {
@BeforeEach
public void setup() {
RestAssured.baseURI = "https://jsonplaceholder.typicode.com";
}
@Test
@DisplayName("Test serializing POJO for POST request")
@Description("Test serializing a POJO to JSON for a POST request")
public void testSerializePost() {
Post post = new Post();
post.setTitle("Test Post");
post.setBody("This is a test");
post.setUserId(1);
given()
.header("Content-Type", "application/json")
.body(post)
.log().all()
.when()
.post("/posts")
.then()
.log().all()
.statusCode(201)
.body("title", equalTo("Test Post"))
.body("userId", equalTo(1));
}
}
Explanation:
body(post)
: Serializes thePost
POJO to JSON using Jackson.- Verifies the response echoes the sent data (as per
jsonplaceholder.typicode.com
behavior). - POJOs simplify request construction for complex payloads.
Example 5: POJO Mapping with Allure
Integrate POJO mapping with Allure reporting.
import io.qameta.allure.Allure;
import io.qameta.allure.Description;
import io.qameta.allure.Feature;
import io.restassured.RestAssured;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import java.io.IOException;
import static io.restassured.RestAssured.*;
import static org.junit.jupiter.api.Assertions.*;
@Feature("POJO Mapping with REST Assured")
public class AllurePOJOTest {
@BeforeEach
public void setup() {
RestAssured.baseURI = "https://jsonplaceholder.typicode.com";
}
@Test
@DisplayName("Test mapping post to POJO with Allure")
@Description("Test mapping a post to POJO with Allure reporting")
public void testMapPostWithAllure() throws IOException {
Post post = given()
.log().all()
.when()
.get("/posts/1")
.then()
.log().all()
.statusCode(200)
.extract().as(Post.class);
assertEquals(1, post.getId(), "Post ID should be 1");
assertNotNull(post.getTitle(), "Title should not be null");
// Attach POJO as JSON to Allure report
ObjectMapper mapper = new ObjectMapper();
String postJson = mapper.writeValueAsString(post);
Allure.addAttachment("Post POJO", "application/json", postJson, ".json");
}
}
Explanation:
ObjectMapper
: Converts the POJO back to JSON for Allure attachment.Allure.addAttachment
: Attaches the serialized POJO to the report.- Run
mvn clean test
andmvn allure:serve
to view the report.
Integrating with CI/CD
Add POJO mapping tests to a GitHub Actions pipeline for automation.
Create or update .github/workflows/ci.yml
:
name: REST Assured POJO CI Pipeline
on:
push:
branches:
- main
pull_request:
branches:
- main
jobs:
build-and-test:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up JDK 11
uses: actions/setup-java@v4
with:
java-version: '11'
distribution: 'temurin'
- name: Cache Maven dependencies
uses: actions/cache@v4
with:
path: ~/.m2/repository
key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}
restore-keys: ${{ runner.os }}-maven-
- name: Run tests
run: mvn clean test
- name: Generate Allure report
run: mvn allure:report
- name: Upload Allure results
if: always()
uses: actions/upload-artifact@v4
with:
name: allure-results
path: target/allure-results
- name: Publish Allure report
if: always()
uses: simple-elf/allure-report-action@v1.7
with:
allure_results: target/allure-results
gh_pages: gh-pages
allure_report: allure-report
- name: Deploy report to GitHub Pages
if: always()
uses: peaceiris/actions-gh-pages@v4
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: allure-report
Explanation:
- Runs POJO mapping tests with
mvn clean test
. - Publishes Allure reports to GitHub Pages for visibility.
Tips for Beginners
- Match JSON Structure: Ensure POJO fields align with API response keys, using
@JsonProperty
for mismatches. - Use Lombok: Consider Lombok to reduce boilerplate code for getters, setters, and constructors.
- Validate Selectively: Assert only critical POJO properties to keep tests focused.
- Debug with Logging: Enable
log().all()
to inspect JSON when mapping fails.
Troubleshooting Tip: If POJO mapping fails, verify the JSON structure with log().all()
and check for missing fields or type mismatches in the POJO.
What’s Next?
This post enhances our REST Assured series by introducing POJO Mapping, a powerful technique for handling API responses. To continue your learning, consider exploring:
- GraphQL Testing: Testing GraphQL APIs with REST Assured.
- Advanced Allure Reporting: Customizing reports with environments and categories.
- End-to-End Testing: Combining REST Assured with Selenium for UI and API testing.