Time to put everything together. This is a complete, production-style test class. It uses specs, logging, Hamcrest matchers, POJO deserialization, and covers the full CRUD cycle. You can copy this, point it at any REST API, and have a working test suite in minutes.
// pojo/Post.java
package com.yourcompany.pojo;
public class Post {
private int userId;
private int id;
private String title;
private String body;
public Post() {}
public Post(int userId, String title, String body) {
this.userId = userId;
this.title = title;
this.body = body;
}
public int getUserId() { return userId; }
public void setUserId(int userId) { this.userId = userId; }
public int getId() { return id; }
public void setId(int id) { this.id = id; }
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; }
@Override
public String toString() {
return "Post{id=" + id + ", title=" + title + ", userId=" + userId + "}";
}
}// tests/JSONPlaceholderCRUDTest.java
package com.yourcompany.tests;
import com.yourcompany.pojo.Post;
import io.restassured.RestAssured;
import io.restassured.builder.RequestSpecBuilder;
import io.restassured.builder.ResponseSpecBuilder;
import io.restassured.http.ContentType;
import io.restassured.response.Response;
import io.restassured.specification.RequestSpecification;
import io.restassured.specification.ResponseSpecification;
import org.testng.Assert;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;
import java.util.List;
import java.util.Map;
import static io.restassured.RestAssured.*;
import static org.hamcrest.Matchers.*;
public class JSONPlaceholderCRUDTest {
private RequestSpecification requestSpec;
private ResponseSpecification responseSpec200;
@BeforeClass
public void setup() {
RestAssured.baseURI = "https://jsonplaceholder.typicode.com";
requestSpec = new RequestSpecBuilder()
.setContentType(ContentType.JSON)
.addHeader("Accept", "application/json")
.log(LogDetail.URI)
.build();
responseSpec200 = new ResponseSpecBuilder()
.expectStatusCode(200)
.expectContentType(ContentType.JSON)
.build();
// Log request and response only when assertions fail
RestAssured.enableLoggingOfRequestAndResponseIfValidationFails();
}
// ======================== GET TESTS ========================
@Test(priority = 1)
public void testGetAllPosts() {
given()
.spec(requestSpec)
.when()
.get("/posts")
.then()
.spec(responseSpec200)
.body("size()", equalTo(100))
.body("id", everyItem(greaterThan(0)))
.body("title", everyItem(notNullValue()));
}
@Test(priority = 2)
public void testGetSinglePost() {
given()
.spec(requestSpec)
.pathParam("id", 1)
.when()
.get("/posts/{id}")
.then()
.spec(responseSpec200)
.body("id", equalTo(1))
.body("userId", equalTo(1))
.body("title", notNullValue())
.body("body", notNullValue());
}
@Test(priority = 3)
public void testGetPostsByUser() {
given()
.spec(requestSpec)
.queryParam("userId", 1)
.when()
.get("/posts")
.then()
.spec(responseSpec200)
.body("size()", equalTo(10))
.body("userId", everyItem(equalTo(1)));
}
@Test(priority = 4)
public void testGetPostComments() {
given()
.spec(requestSpec)
.pathParam("postId", 1)
.when()
.get("/posts/{postId}/comments")
.then()
.spec(responseSpec200)
.body("size()", equalTo(5))
.body("email", everyItem(notNullValue()))
.body("postId", everyItem(equalTo(1)));
}
// ======================== POST TESTS ========================
@Test(priority = 5)
public void testCreatePostWithPojo() {
Post newPost = new Post(1, "Automated Test Post", "Created by REST Assured");
Post created =
given()
.spec(requestSpec)
.body(newPost)
.when()
.post("/posts")
.then()
.statusCode(201)
.body("title", equalTo("Automated Test Post"))
.body("id", notNullValue())
.extract()
.as(Post.class);
Assert.assertEquals(created.getTitle(), "Automated Test Post");
Assert.assertTrue(created.getId() > 0, "ID should be positive");
System.out.println("Created: " + created);
}
@Test(priority = 6)
public void testCreatePostWithMap() {
Map<String, Object> body = Map.of(
"title", "Map-based Post",
"body", "Created using HashMap",
"userId", 2
);
given()
.spec(requestSpec)
.body(body)
.when()
.post("/posts")
.then()
.statusCode(201)
.body("title", equalTo("Map-based Post"))
.body("userId", equalTo(2));
}
// ======================== PUT / PATCH TESTS ========================
@Test(priority = 7)
public void testFullUpdateWithPut() {
Map<String, Object> fullUpdate = Map.of(
"id", 1,
"title", "Fully Updated Title",
"body", "Fully updated body",
"userId", 1
);
given()
.spec(requestSpec)
.body(fullUpdate)
.pathParam("id", 1)
.when()
.put("/posts/{id}")
.then()
.spec(responseSpec200)
.body("title", equalTo("Fully Updated Title"));
}
@Test(priority = 8)
public void testPartialUpdateWithPatch() {
given()
.spec(requestSpec)
.body(Map.of("title", "Patched Title Only"))
.pathParam("id", 1)
.when()
.patch("/posts/{id}")
.then()
.spec(responseSpec200)
.body("title", equalTo("Patched Title Only"))
.body("userId", equalTo(1)); // Unchanged
}
// ======================== DELETE TESTS ========================
@Test(priority = 9)
public void testDeletePost() {
given()
.spec(requestSpec)
.pathParam("id", 1)
.when()
.delete("/posts/{id}")
.then()
.statusCode(200);
}
// ======================== EXTRACTION TESTS ========================
@Test(priority = 10)
public void testExtractAndUseData() {
// Extract all post IDs
Response response =
given()
.spec(requestSpec)
.when()
.get("/posts")
.then()
.spec(responseSpec200)
.extract().response();
List<Integer> allIds = response.jsonPath().getList("id", Integer.class);
Assert.assertEquals(allIds.size(), 100);
Assert.assertTrue(allIds.contains(1));
// Use extracted ID in next request
int firstId = allIds.get(0);
given()
.spec(requestSpec)
.pathParam("id", firstId)
.when()
.get("/posts/{id}")
.then()
.spec(responseSpec200)
.body("id", equalTo(firstId));
}
}# Run all tests in the class
mvn test -Dtest=JSONPlaceholderCRUDTest
# Run a specific test method
mvn test -Dtest=JSONPlaceholderCRUDTest#testGetAllPosts
# Run all tests in the project
mvn testRequestSpecBuilder for reusable request configuration
ResponseSpecBuilder for reusable response validation
GET with path params, query params, and nested resources
POST with POJO and HashMap bodies
PUT for full updates and PATCH for partial updates
DELETE operations
Response extraction with jsonPath and POJO deserialization
Smart logging — only on validation failure
Test ordering with priority attribute
Key Point: This is a reference test class. Copy it. Modify the baseURI. Adjust the endpoints. You now have a working CRUD test suite for any REST API.
Test priority in TestNG (priority = 1, 2, 3...) controls execution order. But be careful — tests should ideally be independent. If testDeletePost runs before testGetSinglePost and they share data, you get flaky tests. Use priority for ordering within a CRUD flow, not as a substitute for proper test isolation.
Key Point: A complete CRUD test class uses BaseTest/specs for setup, covers GET/POST/PUT/PATCH/DELETE, uses Hamcrest matchers, extracts data with jsonPath, and logs only on failure.