CRUD tests check values. Schema tests check structure. "Is the title a string?" "Does the user object have an address?" "Is the email format correct?" These are contract checks. If the API changes its response shape, schema tests catch it immediately.
Create one schema file per resource. Put them in src/test/resources/schemas/.
{
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"required": ["userId", "id", "title", "body"],
"properties": {
"userId": {
"type": "integer",
"minimum": 1
},
"id": {
"type": "integer",
"minimum": 1
},
"title": {
"type": "string",
"minLength": 1
},
"body": {
"type": "string",
"minLength": 1
}
},
"additionalProperties": false
}{
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"required": ["id", "name", "username", "email", "address", "phone", "website", "company"],
"properties": {
"id": { "type": "integer" },
"name": { "type": "string" },
"username": { "type": "string" },
"email": { "type": "string", "format": "email" },
"address": {
"type": "object",
"required": ["street", "suite", "city", "zipcode", "geo"],
"properties": {
"street": { "type": "string" },
"suite": { "type": "string" },
"city": { "type": "string" },
"zipcode": { "type": "string" },
"geo": {
"type": "object",
"required": ["lat", "lng"],
"properties": {
"lat": { "type": "string" },
"lng": { "type": "string" }
}
}
}
},
"phone": { "type": "string" },
"website": { "type": "string" },
"company": {
"type": "object",
"required": ["name", "catchPhrase", "bs"],
"properties": {
"name": { "type": "string" },
"catchPhrase": { "type": "string" },
"bs": { "type": "string" }
}
}
}
}{
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"required": ["postId", "id", "name", "email", "body"],
"properties": {
"postId": { "type": "integer" },
"id": { "type": "integer" },
"name": { "type": "string" },
"email": { "type": "string", "format": "email" },
"body": { "type": "string" }
},
"additionalProperties": false
}package com.capstone.api.tests;
import com.capstone.api.base.BaseTest;
import io.qameta.allure.Epic;
import io.qameta.allure.Feature;
import io.qameta.allure.Severity;
import io.qameta.allure.SeverityLevel;
import org.testng.annotations.Test;
import static io.restassured.RestAssured.given;
import static io.restassured.module.jsv.JsonSchemaValidator.matchesJsonSchemaInClasspath;
import static org.hamcrest.Matchers.equalTo;
@Epic("JSONPlaceholder API")
@Feature("Schema Validation")
public class PostSchemaTests extends BaseTest {
@Test(groups = "regression")
@Severity(SeverityLevel.CRITICAL)
public void testPostResponseMatchesSchema() {
given().spec(requestSpec)
.when()
.get("/posts/1")
.then()
.statusCode(200)
.body(matchesJsonSchemaInClasspath("schemas/post-schema.json"));
}
@Test(groups = "regression")
@Severity(SeverityLevel.CRITICAL)
public void testUserResponseMatchesSchema() {
given().spec(requestSpec)
.when()
.get("/users/1")
.then()
.statusCode(200)
.body(matchesJsonSchemaInClasspath("schemas/user-schema.json"));
}
@Test(groups = "regression")
@Severity(SeverityLevel.CRITICAL)
public void testCommentResponseMatchesSchema() {
given().spec(requestSpec)
.when()
.get("/comments/1")
.then()
.statusCode(200)
.body(matchesJsonSchemaInClasspath("schemas/comment-schema.json"));
}
@Test(groups = "regression")
@Severity(SeverityLevel.NORMAL)
public void testAllPostsArrayContainsValidPosts() {
given().spec(requestSpec)
.when()
.get("/posts")
.then()
.statusCode(200)
.body("[0]", matchesJsonSchemaInClasspath("schemas/post-schema.json"))
.body("[99]", matchesJsonSchemaInClasspath("schemas/post-schema.json"));
}
@Test(groups = "regression")
@Severity(SeverityLevel.NORMAL)
public void testNestedUserAddressHasGeo() {
given().spec(requestSpec)
.when()
.get("/users/1")
.then()
.statusCode(200)
.body("address.geo.lat", org.hamcrest.Matchers.notNullValue())
.body("address.geo.lng", org.hamcrest.Matchers.notNullValue());
}
}Schema files with "additionalProperties": false are strict — they reject any fields not listed in the schema. Use this when you want to catch the API adding unexpected fields. Remove it when the API is still evolving and you only want to validate known fields.
The user-schema.json does not have "additionalProperties": false because the user object is complex with nested objects. Adding that constraint on deeply nested objects can make the schema brittle. Start without it, add it later when the API stabilizes.
Q: How do you do schema validation in your API tests?
A: We create JSON Schema files for each API resource and store them in src/test/resources/schemas/. Each schema defines required fields, data types, format constraints, and optionally additionalProperties:false for strict validation. In REST Assured, we use matchesJsonSchemaInClasspath() from the json-schema-validator module. We validate both single-object responses and elements within array responses. Schema tests run as part of the regression suite. When the API team changes the response structure, schema tests fail immediately — acting as a lightweight contract check without a full Pact setup.
Key Point: Create JSON Schema files for every resource. Use matchesJsonSchemaInClasspath() in REST Assured. Schema validation is your contract test — it catches structural changes that value-based assertions miss.