Workflows
3 min
API Response Validation
Common patterns for validating API responses
...
apivalidationtestingbest-practices
API Response Validation Patterns
1. Status Code Validation
Common HTTP Status Codes:
// Success codes
200 OK // Successful GET, PUT, PATCH
201 Created // Successful POST (resource created)
204 No Content // Successful DELETE or update with no body
// Client error codes
400 Bad Request // Invalid request data
401 Unauthorized // Missing or invalid authentication
403 Forbidden // Insufficient permissions
404 Not Found // Resource doesn't exist
409 Conflict // Duplicate or conflicting state
422 Unprocessable // Validation errors
// Server error codes
500 Internal Error // Server-side error
503 Service Unavailable // Temporary unavailabilityREST Assured (Java):
given()
.baseUri("https://api.example.com")
.when()
.get("/users/123")
.then()
.statusCode(200)
.statusCode(in(Arrays.asList(200, 201))) // Multiple valid codes
.statusCode(lessThan(300)); // Any success codeJavaScript (with Chai):
const response = await fetch('https://api.example.com/users/123');
expect(response.status).to.equal(200);
expect(response.ok).to.be.true; // 200-299 rangePython (requests):
response = requests.get('https://api.example.com/users/123')
assert response.status_code == 200
assert response.ok # True for 200-399
response.raise_for_status() # Raises exception if error2. Response Body Validation
Exact Value Matching:
// REST Assured
.then()
.body("status", equalTo("success"))
.body("data.user.id", equalTo(123))
.body("data.user.email", equalTo("john@example.com"));// JavaScript
const data = await response.json();
expect(data.status).to.equal("success");
expect(data.data.user.id).to.equal(123);# Python
data = response.json()
assert data["status"] == "success"
assert data["data"]["user"]["id"] == 123Partial Matching:
.then()
.body("email", containsString("@example.com"))
.body("username", startsWith("user_"))
.body("description", not(emptyOrNullString()));Null and Existence Checks:
.then()
.body("data.user", notNullValue())
.body("data.user.id", not(emptyOrNullString()))
.body("optionalField", nullValue())
.body("$", hasKey("status"))
.body("data.user", hasKey("email"));3. Array Validation
Array Size:
.then()
.body("users.size()", equalTo(10))
.body("users.size()", greaterThan(0))
.body("users.size()", lessThanOrEqualTo(100));Array Contains:
.then()
.body("users.id", hasItem(123))
.body("users.id", hasItems(123, 456, 789))
.body("roles", contains("admin", "user")) // Exact order
.body("roles", containsInAnyOrder("user", "admin")); // Any orderAll Elements Match:
.then()
.body("users.email", everyItem(containsString("@")))
.body("users.age", everyItem(greaterThan(0)))
.body("users.status", everyItem(isIn(Arrays.asList("active", "inactive"))));Array Filtering:
// Find elements matching condition
.then()
.body("users.findAll { it.status == 'active' }.size()", greaterThan(0))
.body("users.find { it.id == 123 }.email", equalTo("john@example.com"));4. Schema Validation
JSON Schema (REST Assured):
import static io.restassured.module.jsv.JsonSchemaValidator.*;
.then()
.body(matchesJsonSchemaInClasspath("user-schema.json"));user-schema.json:
{
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"required": ["status", "data"],
"properties": {
"status": {
"type": "string",
"enum": ["success", "error"]
},
"data": {
"type": "object",
"required": ["user"],
"properties": {
"user": {
"type": "object",
"required": ["id", "email", "username"],
"properties": {
"id": { "type": "integer" },
"email": {
"type": "string",
"format": "email"
},
"username": {
"type": "string",
"minLength": 3,
"maxLength": 20
},
"age": {
"type": "integer",
"minimum": 0,
"maximum": 150
}
}
}
}
}
}
}JavaScript (AJV):
const Ajv = require('ajv');
const ajv = new Ajv();
const schema = {
type: 'object',
required: ['status', 'data'],
properties: {
status: { type: 'string' },
data: { type: 'object' }
}
};
const validate = ajv.compile(schema);
const valid = validate(responseData);
expect(valid).to.be.true;5. Header Validation
.then()
.header("Content-Type", equalTo("application/json"))
.header("Content-Type", containsString("application/json"))
.header("X-Rate-Limit-Remaining", Integer::parseInt, greaterThan(0))
.headers("Content-Type", "application/json",
"Connection", "keep-alive");expect(response.headers.get('content-type')).to.include('application/json');
expect(response.headers.get('x-rate-limit-limit')).to.exist;6. Response Time Validation
.then()
.time(lessThan(1000L)) // Less than 1 second
.time(lessThan(1000L), TimeUnit.MILLISECONDS);const start = Date.now();
const response = await fetch(url);
const duration = Date.now() - start;
expect(duration).to.be.below(1000);7. Complex Nested Validation
Sample Response:
{
"data": {
"orders": [
{
"id": "ORD-001",
"customer": {
"name": "John Doe",
"email": "john@example.com"
},
"items": [
{ "product": "Item A", "quantity": 2, "price": 10.00 },
{ "product": "Item B", "quantity": 1, "price": 20.00 }
],
"total": 40.00,
"status": "completed"
}
]
}
}Validation:
.then()
// Nested object validation
.body("data.orders[0].customer.name", equalTo("John Doe"))
.body("data.orders[0].customer.email", containsString("@"))
// Nested array validation
.body("data.orders[0].items.size()", equalTo(2))
.body("data.orders[0].items.product", hasItems("Item A", "Item B"))
.body("data.orders[0].items.quantity", everyItem(greaterThan(0)))
// Calculated field validation
.body("data.orders[0].total", equalTo(40.00f))
// Complex filtering
.body("data.orders[0].items.findAll { it.price > 15 }.size()",
equalTo(1));8. Date/Time Validation
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
.then()
.body("createdAt", notNullValue())
.body("createdAt", matchesPattern("\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}.*"));
// Extract and parse
String dateStr = response.path("createdAt");
LocalDateTime date = LocalDateTime.parse(dateStr,
DateTimeFormatter.ISO_DATE_TIME);
assertTrue(date.isBefore(LocalDateTime.now()));9. Custom Matchers
// REST Assured custom matcher
public static Matcher<String> isValidEmail() {
return new BaseMatcher<String>() {
@Override
public boolean matches(Object item) {
String email = (String) item;
return email.matches("^[A-Za-z0-9+_.-]+@(.+)$");
}
@Override
public void describeTo(Description description) {
description.appendText("a valid email address");
}
};
}
// Usage
.then()
.body("email", isValidEmail());10. Validation Helper Functions
Reusable Validation Functions:
public class APIValidators {
public static void validateSuccessResponse(Response response) {
response.then()
.statusCode(200)
.body("status", equalTo("success"))
.header("Content-Type", containsString("application/json"));
}
public static void validatePaginatedResponse(Response response,
int expectedSize) {
response.then()
.statusCode(200)
.body("data.size()", equalTo(expectedSize))
.body("pagination.page", greaterThan(0))
.body("pagination.total", greaterThanOrEqualTo(expectedSize));
}
public static void validateErrorResponse(Response response,
int statusCode,
String errorMessage) {
response.then()
.statusCode(statusCode)
.body("status", equalTo("error"))
.body("error.message", containsString(errorMessage));
}
}
// Usage
Response response = given().get("/users");
APIValidators.validateSuccessResponse(response);Quick Reference
Common Validation Patterns
| Pattern | Use Case | Example |
|---|---|---|
equalTo() | Exact match | .body("id", equalTo(123)) |
containsString() | Partial match | .body("msg", containsString("success")) |
hasItem() | Array contains | .body("ids", hasItem(123)) |
everyItem() | All elements | .body("ages", everyItem(greaterThan(0))) |
notNullValue() | Not null | .body("data", notNullValue()) |
hasKey() | Has property | .body("$", hasKey("status")) |
matchesPattern() | Regex | .body("code", matchesPattern("[A-Z]{3}")) |
Best Practices
✅ Do:
- Validate status code first
- Check response structure (schema)
- Validate critical fields
- Test error responses
- Use meaningful assertions
- Extract and reuse common validations
❌ Don't:
- Only check status code
- Ignore response headers
- Skip error scenarios
- Use vague assertions
- Validate every single field (test what matters)
Key Takeaways
- Always validate status code first
- Use schema validation for structure verification
- Test both positive and negative scenarios
- Validate arrays with size and content checks
- Check response times for performance
- Create reusable validation functions
- Validate headers and content types
- Use custom matchers for complex validations
Comments (0)
Loading comments...