by thetestingacademy
Java REST API testing with REST Assured including JSON schema validation
npx @qaskills/cli add rest-assured-apiAuto-detects your AI agent and installs the skill. Works with Claude Code, Cursor, Copilot, and more.
You are an expert QA automation engineer specializing in REST Assured API testing with Java. When the user asks you to write, review, or debug REST Assured tests, follow these detailed instructions.
src/
main/java/com/example/
models/
User.java
Product.java
ApiError.java
specs/
RequestSpecs.java
ResponseSpecs.java
utils/
AuthHelper.java
TestDataHelper.java
test/java/com/example/
tests/
BaseApiTest.java
UsersApiTest.java
ProductsApiTest.java
AuthApiTest.java
schemas/
user-schema.json
product-schema.json
error-schema.json
test/resources/
test-data/
users.json
config.properties
pom.xml
<dependencies>
<dependency>
<groupId>io.rest-assured</groupId>
<artifactId>rest-assured</artifactId>
<version>5.4.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.rest-assured</groupId>
<artifactId>json-schema-validator</artifactId>
<version>5.4.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.rest-assured</groupId>
<artifactId>json-path</artifactId>
<version>5.4.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.testng</groupId>
<artifactId>testng</artifactId>
<version>7.9.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.hamcrest</groupId>
<artifactId>hamcrest</artifactId>
<version>2.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.16.1</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.30</version>
<scope>provided</scope>
</dependency>
</dependencies>
package com.example.models;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import lombok.Data;
import lombok.Builder;
import lombok.NoArgsConstructor;
import lombok.AllArgsConstructor;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@JsonIgnoreProperties(ignoreUnknown = true)
public class User {
private String id;
private String email;
private String firstName;
private String lastName;
private String role;
private boolean active;
private String createdAt;
private String updatedAt;
}
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class CreateUserRequest {
private String email;
private String firstName;
private String lastName;
private String password;
private String role;
}
@Data
@NoArgsConstructor
@AllArgsConstructor
@JsonIgnoreProperties(ignoreUnknown = true)
public class ApiError {
private int statusCode;
private String message;
private String error;
private Map<String, List<String>> details;
}
package com.example.specs;
import io.restassured.builder.RequestSpecBuilder;
import io.restassured.builder.ResponseSpecBuilder;
import io.restassured.filter.log.LogDetail;
import io.restassured.http.ContentType;
import io.restassured.specification.RequestSpecification;
import io.restassured.specification.ResponseSpecification;
import static org.hamcrest.Matchers.*;
public class RequestSpecs {
public static RequestSpecification baseSpec() {
return new RequestSpecBuilder()
.setBaseUri(System.getProperty("base.url", "http://localhost:3000"))
.setBasePath("/api")
.setContentType(ContentType.JSON)
.setAccept(ContentType.JSON)
.log(LogDetail.ALL)
.build();
}
public static RequestSpecification authSpec(String token) {
return new RequestSpecBuilder()
.addRequestSpecification(baseSpec())
.addHeader("Authorization", "Bearer " + token)
.build();
}
public static RequestSpecification adminSpec() {
String token = AuthHelper.getAdminToken();
return authSpec(token);
}
}
public class ResponseSpecs {
public static ResponseSpecification successResponse() {
return new ResponseSpecBuilder()
.expectStatusCode(200)
.expectContentType(ContentType.JSON)
.expectResponseTime(lessThan(5000L))
.log(LogDetail.ALL)
.build();
}
public static ResponseSpecification createdResponse() {
return new ResponseSpecBuilder()
.expectStatusCode(201)
.expectContentType(ContentType.JSON)
.build();
}
public static ResponseSpecification noContentResponse() {
return new ResponseSpecBuilder()
.expectStatusCode(204)
.build();
}
public static ResponseSpecification notFoundResponse() {
return new ResponseSpecBuilder()
.expectStatusCode(404)
.expectContentType(ContentType.JSON)
.expectBody("message", notNullValue())
.build();
}
public static ResponseSpecification validationErrorResponse() {
return new ResponseSpecBuilder()
.expectStatusCode(400)
.expectContentType(ContentType.JSON)
.expectBody("message", notNullValue())
.build();
}
}
package com.example.tests;
import com.example.specs.RequestSpecs;
import io.restassured.RestAssured;
import io.restassured.specification.RequestSpecification;
import org.testng.annotations.BeforeClass;
public abstract class BaseApiTest {
protected RequestSpecification requestSpec;
@BeforeClass
public void setUp() {
RestAssured.enableLoggingOfRequestAndResponseIfValidationFails();
requestSpec = RequestSpecs.baseSpec();
}
protected String getAuthToken() {
return given()
.spec(requestSpec)
.body(Map.of(
"email", "admin@example.com",
"password", "AdminPass123!"
))
.when()
.post("/auth/login")
.then()
.statusCode(200)
.extract()
.path("token");
}
}
package com.example.tests;
import com.example.models.User;
import com.example.models.CreateUserRequest;
import com.example.specs.ResponseSpecs;
import io.restassured.response.Response;
import org.testng.annotations.Test;
import static io.restassured.RestAssured.given;
import static org.hamcrest.Matchers.*;
public class UsersApiTest extends BaseApiTest {
@Test(description = "Create a new user")
public void testCreateUser() {
CreateUserRequest newUser = CreateUserRequest.builder()
.email("test-" + System.currentTimeMillis() + "@example.com")
.firstName("Test")
.lastName("User")
.password("SecurePass123!")
.role("user")
.build();
User createdUser = given()
.spec(requestSpec)
.body(newUser)
.when()
.post("/users")
.then()
.spec(ResponseSpecs.createdResponse())
.body("id", notNullValue())
.body("email", equalTo(newUser.getEmail()))
.body("firstName", equalTo(newUser.getFirstName()))
.body("role", equalTo("user"))
.extract()
.as(User.class);
assertThat(createdUser.getId()).isNotEmpty();
assertThat(createdUser.getEmail()).isEqualTo(newUser.getEmail());
}
@Test(description = "Get user by ID")
public void testGetUserById() {
// First create a user
String userId = createTestUser();
given()
.spec(requestSpec)
.pathParam("id", userId)
.when()
.get("/users/{id}")
.then()
.spec(ResponseSpecs.successResponse())
.body("id", equalTo(userId))
.body("email", notNullValue())
.body("firstName", notNullValue());
}
@Test(description = "Update user")
public void testUpdateUser() {
String userId = createTestUser();
given()
.spec(requestSpec)
.pathParam("id", userId)
.body(Map.of("firstName", "Updated"))
.when()
.patch("/users/{id}")
.then()
.spec(ResponseSpecs.successResponse())
.body("firstName", equalTo("Updated"));
}
@Test(description = "Delete user")
public void testDeleteUser() {
String userId = createTestUser();
given()
.spec(requestSpec)
.pathParam("id", userId)
.when()
.delete("/users/{id}")
.then()
.spec(ResponseSpecs.noContentResponse());
// Verify deletion
given()
.spec(requestSpec)
.pathParam("id", userId)
.when()
.get("/users/{id}")
.then()
.spec(ResponseSpecs.notFoundResponse());
}
@Test(description = "List users with pagination")
public void testListUsersWithPagination() {
given()
.spec(requestSpec)
.queryParam("page", 1)
.queryParam("pageSize", 5)
.when()
.get("/users")
.then()
.spec(ResponseSpecs.successResponse())
.body("data", hasSize(lessThanOrEqualTo(5)))
.body("page", equalTo(1))
.body("pageSize", equalTo(5))
.body("total", greaterThanOrEqualTo(0));
}
private String createTestUser() {
CreateUserRequest user = CreateUserRequest.builder()
.email("test-" + System.currentTimeMillis() + "@example.com")
.firstName("Test")
.lastName("User")
.password("SecurePass123!")
.build();
return given()
.spec(requestSpec)
.body(user)
.post("/users")
.then()
.statusCode(201)
.extract()
.path("id");
}
}
// schemas/user-schema.json
{
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"required": ["id", "email", "firstName", "lastName", "role", "createdAt"],
"properties": {
"id": { "type": "string", "format": "uuid" },
"email": { "type": "string", "format": "email" },
"firstName": { "type": "string", "minLength": 1 },
"lastName": { "type": "string", "minLength": 1 },
"role": { "type": "string", "enum": ["admin", "user", "viewer"] },
"active": { "type": "boolean" },
"createdAt": { "type": "string", "format": "date-time" }
},
"additionalProperties": false
}
import static io.restassured.module.jsv.JsonSchemaValidator.matchesJsonSchemaInClasspath;
@Test(description = "Response should match JSON schema")
public void testUserResponseSchema() {
given()
.spec(requestSpec)
.when()
.get("/users/" + knownUserId)
.then()
.statusCode(200)
.body(matchesJsonSchemaInClasspath("schemas/user-schema.json"));
}
@Test(description = "Should authenticate with valid credentials")
public void testValidLogin() {
given()
.spec(requestSpec)
.body(Map.of(
"email", "admin@example.com",
"password", "AdminPass123!"
))
.when()
.post("/auth/login")
.then()
.statusCode(200)
.body("token", notNullValue())
.body("expiresIn", greaterThan(0))
.body("user.email", equalTo("admin@example.com"))
.body("user.role", equalTo("admin"));
}
@Test(description = "Should reject invalid credentials")
public void testInvalidLogin() {
given()
.spec(requestSpec)
.body(Map.of(
"email", "admin@example.com",
"password", "wrongpassword"
))
.when()
.post("/auth/login")
.then()
.statusCode(401)
.body("message", equalTo("Invalid credentials"));
}
@Test(description = "Protected endpoint requires auth token")
public void testProtectedEndpoint() {
// Without token
given()
.spec(requestSpec)
.when()
.get("/users/me")
.then()
.statusCode(401);
// With valid token
String token = getAuthToken();
given()
.spec(requestSpec)
.header("Authorization", "Bearer " + token)
.when()
.get("/users/me")
.then()
.statusCode(200)
.body("email", notNullValue());
}
@Test(description = "Should return validation errors for invalid input")
public void testCreateUserValidation() {
// Missing required fields
given()
.spec(requestSpec)
.body(Map.of("firstName", "Test"))
.when()
.post("/users")
.then()
.statusCode(400)
.body("details.email", notNullValue())
.body("details.password", notNullValue());
}
@Test(description = "Should reject invalid email format")
public void testInvalidEmailFormat() {
given()
.spec(requestSpec)
.body(Map.of(
"email", "not-an-email",
"firstName", "Test",
"lastName", "User",
"password", "SecurePass123!"
))
.when()
.post("/users")
.then()
.statusCode(400)
.body("details.email[0]", containsString("valid email"));
}
// Extract single value
String userId = given()
.spec(requestSpec)
.get("/users/me")
.then()
.extract()
.path("id");
// Extract as POJO
User user = given()
.spec(requestSpec)
.get("/users/me")
.then()
.extract()
.as(User.class);
// Extract list
List<User> users = given()
.spec(requestSpec)
.get("/users")
.then()
.extract()
.jsonPath()
.getList("data", User.class);
// Extract headers
String requestId = given()
.spec(requestSpec)
.get("/users")
.then()
.extract()
.header("X-Request-Id");
// Extract response time
long responseTime = given()
.spec(requestSpec)
.get("/health")
.then()
.extract()
.time();
@Test(description = "Should upload a file")
public void testFileUpload() {
File file = new File("src/test/resources/test-data/sample.pdf");
given()
.spec(requestSpec)
.contentType(ContentType.MULTIPART)
.multiPart("file", file, "application/pdf")
.formParam("description", "Test upload")
.when()
.post("/files/upload")
.then()
.statusCode(201)
.body("filename", equalTo("sample.pdf"))
.body("size", greaterThan(0));
}
RequestSpecBuilder and ResponseSpecBuilder for DRY code.enableLoggingOfRequestAndResponseIfValidationFails().@AfterMethod.body() with raw strings -- Use POJOs or Map.of() for request bodies.- name: Install QA Skills
run: npx @qaskills/cli add rest-assured-api10 of 29 agents supported