API Test Automation
Master the art of automated API testing with REST and GraphQL
Overview
API (Application Programming Interface) testing is crucial for ensuring that data exchange between different software components works correctly. This module covers comprehensive API testing strategies, from basic HTTP requests to complex authentication scenarios.
What you'll learn: REST API testing, GraphQL testing, authentication methods, data validation, error handling, and performance testing for APIs.
REST API Testing
REST APIs are the backbone of modern web applications. Here's how to test them effectively:
Basic GET Request Testing
// Using Fetch API with Jest
const request = require('supertest');
const app = require('../app');
describe('GET /api/users', () => {
test('should return list of users', async () => {
const response = await request(app)
.get('/api/users')
.expect(200);
expect(response.body).toHaveProperty('users');
expect(Array.isArray(response.body.users)).toBe(true);
expect(response.body.users.length).toBeGreaterThan(0);
// Validate first user structure
const firstUser = response.body.users[0];
expect(firstUser).toHaveProperty('id');
expect(firstUser).toHaveProperty('name');
expect(firstUser).toHaveProperty('email');
});
test('should return user by ID', async () => {
const userId = 1;
const response = await request(app)
.get(`/api/users/${userId}`)
.expect(200);
expect(response.body).toHaveProperty('id', userId);
expect(response.body).toHaveProperty('name');
expect(response.body).toHaveProperty('email');
// Validate email format
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
expect(response.body.email).toMatch(emailRegex);
});
test('should return 404 for non-existent user', async () => {
const nonExistentId = 99999;
const response = await request(app)
.get(`/api/users/${nonExistentId}`)
.expect(404);
expect(response.body).toHaveProperty('error');
});
test('should validate response time', async () => {
const startTime = Date.now();
await request(app)
.get('/api/users')
.expect(200);
const responseTime = Date.now() - startTime;
expect(responseTime).toBeLessThan(1000); // Less than 1 second
});
});
// Using REST Assured with TestNG
import static io.restassured.RestAssured.*;
import static org.hamcrest.Matchers.*;
import org.testng.annotations.Test;
import io.restassured.response.Response;
public class UserApiTest {
private static final String BASE_URL = "https://api.example.com";
@Test
public void testGetAllUsers() {
given()
.baseUri(BASE_URL)
.header("Content-Type", "application/json")
.when()
.get("/api/users")
.then()
.statusCode(200)
.body("users", hasSize(greaterThan(0)))
.body("users[0]", hasKey("id"))
.body("users[0]", hasKey("name"))
.body("users[0]", hasKey("email"))
.time(lessThan(1000L)); // Response time validation
}
@Test
public void testGetUserById() {
int userId = 1;
given()
.baseUri(BASE_URL)
.pathParam("id", userId)
.when()
.get("/api/users/{id}")
.then()
.statusCode(200)
.body("id", equalTo(userId))
.body("name", not(emptyString()))
.body("email", matchesPattern(".*@.*\\..*"));
}
@Test
public void testGetUserNotFound() {
int nonExistentId = 99999;
given()
.baseUri(BASE_URL)
.pathParam("id", nonExistentId)
.when()
.get("/api/users/{id}")
.then()
.statusCode(404)
.body("error", not(emptyString()));
}
@Test
public void testGetUsersWithPagination() {
given()
.baseUri(BASE_URL)
.queryParam("page", 1)
.queryParam("limit", 10)
.when()
.get("/api/users")
.then()
.statusCode(200)
.body("users", hasSize(lessThanOrEqualTo(10)))
.body("pagination.page", equalTo(1))
.body("pagination.limit", equalTo(10));
}
}
# Using Requests with pytest
import requests
import pytest
import time
import re
class TestUserAPI:
BASE_URL = "https://api.example.com"
def test_get_all_users(self):
response = requests.get(f"{self.BASE_URL}/api/users")
assert response.status_code == 200
data = response.json()
assert "users" in data
assert isinstance(data["users"], list)
assert len(data["users"]) > 0
# Validate user structure
user = data["users"][0]
assert "id" in user
assert "name" in user
assert "email" in user
# Validate data types
assert isinstance(user["id"], int)
assert isinstance(user["name"], str)
assert isinstance(user["email"], str)
def test_get_user_by_id(self):
user_id = 1
response = requests.get(f"{self.BASE_URL}/api/users/{user_id}")
assert response.status_code == 200
user = response.json()
assert user["id"] == user_id
assert len(user["name"]) > 0
assert "@" in user["email"]
# Validate email format with regex
email_pattern = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'
assert re.match(email_pattern, user["email"])
def test_get_user_not_found(self):
non_existent_id = 99999
response = requests.get(f"{self.BASE_URL}/api/users/{non_existent_id}")
assert response.status_code == 404
error_data = response.json()
assert "error" in error_data
def test_response_time(self):
start_time = time.time()
response = requests.get(f"{self.BASE_URL}/api/users")
response_time = time.time() - start_time
assert response.status_code == 200
assert response_time < 1.0 # Less than 1 second
def test_get_users_with_pagination(self):
params = {"page": 1, "limit": 10}
response = requests.get(
f"{self.BASE_URL}/api/users",
params=params
)
assert response.status_code == 200
data = response.json()
assert len(data["users"]) <= 10
assert data["pagination"]["page"] == 1
assert data["pagination"]["limit"] == 10
POST Request Testing with Data Validation
// Testing POST requests with validation
describe('POST /api/users', () => {
test('should create new user successfully', async () => {
const newUser = {
name: 'John Doe',
email: 'john.doe@example.com',
age: 30,
role: 'user'
};
const response = await request(app)
.post('/api/users')
.send(newUser)
.expect(201);
expect(response.body).toHaveProperty('id');
expect(response.body.name).toBe(newUser.name);
expect(response.body.email).toBe(newUser.email);
expect(response.body.age).toBe(newUser.age);
expect(response.body.role).toBe(newUser.role);
});
test('should return 400 for invalid email', async () => {
const invalidUser = {
name: 'John Doe',
email: 'invalid-email',
age: 30
};
const response = await request(app)
.post('/api/users')
.send(invalidUser)
.expect(400);
expect(response.body).toHaveProperty('error');
expect(response.body.error).toContain('email');
});
test('should return 400 for missing required fields', async () => {
const incompleteUser = {
name: 'John Doe'
// Missing email and other required fields
};
const response = await request(app)
.post('/api/users')
.send(incompleteUser)
.expect(400);
expect(response.body).toHaveProperty('error');
expect(response.body.error).toContain('required');
});
test('should return 409 for duplicate email', async () => {
const duplicateUser = {
name: 'Jane Doe',
email: 'existing@example.com', // Assume this email already exists
age: 25
};
const response = await request(app)
.post('/api/users')
.send(duplicateUser)
.expect(409);
expect(response.body).toHaveProperty('error');
expect(response.body.error).toContain('already exists');
});
test('should validate age constraints', async () => {
const invalidAgeUser = {
name: 'Too Young',
email: 'young@example.com',
age: -5 // Invalid age
};
const response = await request(app)
.post('/api/users')
.send(invalidAgeUser)
.expect(400);
expect(response.body).toHaveProperty('error');
expect(response.body.error).toContain('age');
});
});
@Test
public void testCreateUserSuccess() {
String requestBody = """
{
"name": "John Doe",
"email": "john.doe@example.com",
"age": 30,
"role": "user"
}
""";
given()
.baseUri(BASE_URL)
.contentType(ContentType.JSON)
.body(requestBody)
.when()
.post("/api/users")
.then()
.statusCode(201)
.body("id", notNullValue())
.body("name", equalTo("John Doe"))
.body("email", equalTo("john.doe@example.com"))
.body("age", equalTo(30))
.body("role", equalTo("user"));
}
@Test
public void testCreateUserInvalidEmail() {
String requestBody = """
{
"name": "John Doe",
"email": "invalid-email",
"age": 30
}
""";
given()
.baseUri(BASE_URL)
.contentType(ContentType.JSON)
.body(requestBody)
.when()
.post("/api/users")
.then()
.statusCode(400)
.body("error", containsString("email"));
}
@Test
public void testCreateUserMissingFields() {
String requestBody = """
{
"name": "John Doe"
}
""";
given()
.baseUri(BASE_URL)
.contentType(ContentType.JSON)
.body(requestBody)
.when()
.post("/api/users")
.then()
.statusCode(400)
.body("error", containsString("required"));
}
@Test
public void testCreateUserDuplicateEmail() {
String requestBody = """
{
"name": "Jane Doe",
"email": "existing@example.com",
"age": 25
}
""";
given()
.baseUri(BASE_URL)
.contentType(ContentType.JSON)
.body(requestBody)
.when()
.post("/api/users")
.then()
.statusCode(409)
.body("error", containsString("already exists"));
}
@Test
public void testCreateUserInvalidAge() {
String requestBody = """
{
"name": "Too Young",
"email": "young@example.com",
"age": -5
}
""";
given()
.baseUri(BASE_URL)
.contentType(ContentType.JSON)
.body(requestBody)
.when()
.post("/api/users")
.then()
.statusCode(400)
.body("error", containsString("age"));
}
@Test
public void testCreateUserResponseStructure() {
String requestBody = """
{
"name": "Test User",
"email": "test.user@example.com",
"age": 28
}
""";
Response response = given()
.baseUri(BASE_URL)
.contentType(ContentType.JSON)
.body(requestBody)
.when()
.post("/api/users")
.then()
.statusCode(201)
.extract().response();
// Validate response structure and content
assertThat(response.jsonPath().getInt("id")).isGreaterThan(0);
assertThat(response.jsonPath().getString("createdAt")).isNotEmpty();
assertThat(response.jsonPath().getString("updatedAt")).isNotEmpty();
}
def test_create_user_success(self):
user_data = {
"name": "John Doe",
"email": "john.doe@example.com",
"age": 30,
"role": "user"
}
response = requests.post(
f"{self.BASE_URL}/api/users",
json=user_data
)
assert response.status_code == 201
created_user = response.json()
assert "id" in created_user
assert created_user["name"] == user_data["name"]
assert created_user["email"] == user_data["email"]
assert created_user["age"] == user_data["age"]
assert created_user["role"] == user_data["role"]
# Validate response structure
assert "createdAt" in created_user
assert "updatedAt" in created_user
def test_create_user_invalid_email(self):
invalid_user = {
"name": "John Doe",
"email": "invalid-email",
"age": 30
}
response = requests.post(
f"{self.BASE_URL}/api/users",
json=invalid_user
)
assert response.status_code == 400
error_response = response.json()
assert "error" in error_response
assert "email" in error_response["error"].lower()
def test_create_user_missing_fields(self):
incomplete_user = {
"name": "John Doe"
# Missing email and other required fields
}
response = requests.post(
f"{self.BASE_URL}/api/users",
json=incomplete_user
)
assert response.status_code == 400
error_response = response.json()
assert "error" in error_response
assert "required" in error_response["error"].lower()
def test_create_user_duplicate_email(self):
duplicate_user = {
"name": "Jane Doe",
"email": "existing@example.com", # Assume this email already exists
"age": 25
}
response = requests.post(
f"{self.BASE_URL}/api/users",
json=duplicate_user
)
assert response.status_code == 409
error_response = response.json()
assert "error" in error_response
assert "already exists" in error_response["error"].lower()
def test_create_user_invalid_age(self):
invalid_age_user = {
"name": "Too Young",
"email": "young@example.com",
"age": -5 # Invalid age
}
response = requests.post(
f"{self.BASE_URL}/api/users",
json=invalid_age_user
)
assert response.status_code == 400
error_response = response.json()
assert "error" in error_response
assert "age" in error_response["error"].lower()
def test_create_user_data_types(self):
"""Test that API validates data types correctly"""
invalid_data_user = {
"name": 12345, # Should be string
"email": "test@example.com",
"age": "thirty" # Should be number
}
response = requests.post(
f"{self.BASE_URL}/api/users",
json=invalid_data_user
)
assert response.status_code == 400
error_response = response.json()
assert "error" in error_response
Authentication Testing
Most APIs require authentication. Here's how to test different authentication methods:
JWT Token Authentication
// JWT Authentication Testing
describe('Authentication Tests', () => {
let authToken;
beforeAll(async () => {
// Login to get authentication token
const loginResponse = await request(app)
.post('/api/auth/login')
.send({
email: 'test@example.com',
password: 'password123'
})
.expect(200);
authToken = loginResponse.body.token;
// Validate token structure
expect(authToken).toBeDefined();
expect(typeof authToken).toBe('string');
expect(authToken.split('.')).toHaveLength(3); // JWT has 3 parts
});
test('should access protected endpoint with valid token', async () => {
const response = await request(app)
.get('/api/protected/profile')
.set('Authorization', `Bearer ${authToken}`)
.expect(200);
expect(response.body).toHaveProperty('user');
expect(response.body.user).toHaveProperty('id');
expect(response.body.user).toHaveProperty('email');
});
test('should reject request without token', async () => {
const response = await request(app)
.get('/api/protected/profile')
.expect(401);
expect(response.body).toHaveProperty('error');
expect(response.body.error).toContain('token');
});
test('should reject request with invalid token', async () => {
const response = await request(app)
.get('/api/protected/profile')
.set('Authorization', 'Bearer invalid-token')
.expect(401);
expect(response.body).toHaveProperty('error');
});
test('should reject request with malformed header', async () => {
const response = await request(app)
.get('/api/protected/profile')
.set('Authorization', authToken) // Missing "Bearer " prefix
.expect(401);
expect(response.body).toHaveProperty('error');
});
test('should handle token expiration', async () => {
// Mock an expired token or wait for real expiration
const expiredToken = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.expired.token';
const response = await request(app)
.get('/api/protected/profile')
.set('Authorization', `Bearer ${expiredToken}`)
.expect(401);
expect(response.body.error).toContain('expired');
});
test('should refresh expired token', async () => {
// Assume we have a refresh token
const refreshResponse = await request(app)
.post('/api/auth/refresh')
.set('Authorization', `Bearer ${authToken}`)
.expect(200);
expect(refreshResponse.body).toHaveProperty('token');
expect(refreshResponse.body.token).not.toBe(authToken);
});
});
public class AuthenticationTest {
private String authToken;
@BeforeClass
public void getAuthToken() {
String loginBody = """
{
"email": "test@example.com",
"password": "password123"
}
""";
Response response = given()
.baseUri(BASE_URL)
.contentType(ContentType.JSON)
.body(loginBody)
.when()
.post("/api/auth/login")
.then()
.statusCode(200)
.body("token", notNullValue())
.extract().response();
authToken = response.jsonPath().getString("token");
// Validate JWT structure
assertThat(authToken.split("\\.")).hasSize(3);
}
@Test
public void testProtectedEndpointWithValidToken() {
given()
.baseUri(BASE_URL)
.header("Authorization", "Bearer " + authToken)
.when()
.get("/api/protected/profile")
.then()
.statusCode(200)
.body("user", notNullValue())
.body("user.id", notNullValue())
.body("user.email", notNullValue());
}
@Test
public void testProtectedEndpointWithoutToken() {
given()
.baseUri(BASE_URL)
.when()
.get("/api/protected/profile")
.then()
.statusCode(401)
.body("error", containsString("token"));
}
@Test
public void testProtectedEndpointWithInvalidToken() {
given()
.baseUri(BASE_URL)
.header("Authorization", "Bearer invalid-token")
.when()
.get("/api/protected/profile")
.then()
.statusCode(401)
.body("error", notNullValue());
}
@Test
public void testProtectedEndpointWithMalformedHeader() {
given()
.baseUri(BASE_URL)
.header("Authorization", authToken) // Missing "Bearer " prefix
.when()
.get("/api/protected/profile")
.then()
.statusCode(401)
.body("error", notNullValue());
}
@Test
public void testTokenExpiration() {
String expiredToken = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.expired.token";
given()
.baseUri(BASE_URL)
.header("Authorization", "Bearer " + expiredToken)
.when()
.get("/api/protected/profile")
.then()
.statusCode(401)
.body("error", containsString("expired"));
}
@Test
public void testTokenRefresh() {
given()
.baseUri(BASE_URL)
.header("Authorization", "Bearer " + authToken)
.when()
.post("/api/auth/refresh")
.then()
.statusCode(200)
.body("token", notNullValue())
.body("token", not(equalTo(authToken)));
}
@Test
public void testRoleBasedAccess() {
// Test admin-only endpoint
given()
.baseUri(BASE_URL)
.header("Authorization", "Bearer " + authToken)
.when()
.get("/api/admin/users")
.then()
.statusCode(anyOf(is(200), is(403))); // Depends on user role
}
}
class TestAuthentication:
def setup_class(self):
# Get authentication token
login_data = {
"email": "test@example.com",
"password": "password123"
}
response = requests.post(
f"{self.BASE_URL}/api/auth/login",
json=login_data
)
assert response.status_code == 200
response_data = response.json()
self.auth_token = response_data["token"]
# Validate JWT structure
assert len(self.auth_token.split('.')) == 3
def test_protected_endpoint_with_valid_token(self):
headers = {"Authorization": f"Bearer {self.auth_token}"}
response = requests.get(
f"{self.BASE_URL}/api/protected/profile",
headers=headers
)
assert response.status_code == 200
data = response.json()
assert "user" in data
assert "id" in data["user"]
assert "email" in data["user"]
def test_protected_endpoint_without_token(self):
response = requests.get(f"{self.BASE_URL}/api/protected/profile")
assert response.status_code == 401
error_data = response.json()
assert "error" in error_data
assert "token" in error_data["error"].lower()
def test_protected_endpoint_with_invalid_token(self):
headers = {"Authorization": "Bearer invalid-token"}
response = requests.get(
f"{self.BASE_URL}/api/protected/profile",
headers=headers
)
assert response.status_code == 401
error_data = response.json()
assert "error" in error_data
def test_protected_endpoint_with_malformed_header(self):
headers = {"Authorization": self.auth_token} # Missing "Bearer " prefix
response = requests.get(
f"{self.BASE_URL}/api/protected/profile",
headers=headers
)
assert response.status_code == 401
error_data = response.json()
assert "error" in error_data
def test_token_expiration(self):
# Mock an expired token
expired_token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.expired.token"
headers = {"Authorization": f"Bearer {expired_token}"}
response = requests.get(
f"{self.BASE_URL}/api/protected/profile",
headers=headers
)
assert response.status_code == 401
error_data = response.json()
assert "expired" in error_data["error"].lower()
def test_token_refresh(self):
headers = {"Authorization": f"Bearer {self.auth_token}"}
response = requests.post(
f"{self.BASE_URL}/api/auth/refresh",
headers=headers
)
assert response.status_code == 200
refresh_data = response.json()
assert "token" in refresh_data
assert refresh_data["token"] != self.auth_token
def test_role_based_access(self):
"""Test role-based access control"""
headers = {"Authorization": f"Bearer {self.auth_token}"}
response = requests.get(
f"{self.BASE_URL}/api/admin/users",
headers=headers
)
# Should be either 200 (if user is admin) or 403 (if not admin)
assert response.status_code in [200, 403]
if response.status_code == 403:
error_data = response.json()
assert "error" in error_data
assert "permission" in error_data["error"].lower() \
or "forbidden" in error_data["error"].lower()
def test_concurrent_requests_with_same_token(self):
"""Test that the same token can be used concurrently"""
import threading
headers = {"Authorization": f"Bearer {self.auth_token}"}
results = []
def make_request():
response = requests.get(
f"{self.BASE_URL}/api/protected/profile",
headers=headers
)
results.append(response.status_code)
# Make 5 concurrent requests
threads = []
for _ in range(5):
thread = threading.Thread(target=make_request)
threads.append(thread)
thread.start()
for thread in threads:
thread.join()
# All requests should succeed
assert all(status == 200 for status in results)
GraphQL Testing
GraphQL APIs require different testing approaches due to their flexible query nature:
// GraphQL Testing with Jest
describe('GraphQL API Tests', () => {
test('should query user data successfully', async () => {
const query = `
query GetUser($id: ID!) {
user(id: $id) {
id
name
email
posts {
id
title
createdAt
}
}
}
`;
const variables = { id: '1' };
const response = await request(app)
.post('/graphql')
.send({ query, variables })
.expect(200);
expect(response.body.data).toHaveProperty('user');
expect(response.body.data.user.id).toBe('1');
expect(response.body.data.user.posts).toBeDefined();
expect(Array.isArray(response.body.data.user.posts)).toBe(true);
});
test('should handle GraphQL mutations', async () => {
const mutation = `
mutation CreatePost($input: PostInput!) {
createPost(input: $input) {
id
title
content
author {
id
name
}
createdAt
}
}
`;
const variables = {
input: {
title: 'Test Post',
content: 'This is a test post content',
authorId: '1'
}
};
const response = await request(app)
.post('/graphql')
.send({ query: mutation, variables })
.expect(200);
expect(response.body.data.createPost).toHaveProperty('id');
expect(response.body.data.createPost.title).toBe(variables.input.title);
expect(response.body.data.createPost.author.id).toBe(variables.input.authorId);
});
test('should handle GraphQL errors gracefully', async () => {
const invalidQuery = `
query GetUser($id: ID!) {
user(id: $id) {
id
nonExistentField
}
}
`;
const variables = { id: '1' };
const response = await request(app)
.post('/graphql')
.send({ query: invalidQuery, variables })
.expect(400);
expect(response.body).toHaveProperty('errors');
expect(Array.isArray(response.body.errors)).toBe(true);
expect(response.body.errors[0]).toHaveProperty('message');
});
test('should support nested queries', async () => {
const nestedQuery = `
query GetUserWithPostsAndComments($userId: ID!) {
user(id: $userId) {
id
name
posts {
id
title
comments {
id
content
author {
id
name
}
}
}
}
}
`;
const variables = { userId: '1' };
const response = await request(app)
.post('/graphql')
.send({ query: nestedQuery, variables })
.expect(200);
const user = response.body.data.user;
expect(user).toHaveProperty('posts');
if (user.posts.length > 0) {
const firstPost = user.posts[0];
expect(firstPost).toHaveProperty('comments');
if (firstPost.comments.length > 0) {
const firstComment = firstPost.comments[0];
expect(firstComment).toHaveProperty('author');
expect(firstComment.author).toHaveProperty('name');
}
}
});
test('should validate input data in mutations', async () => {
const invalidMutation = `
mutation CreatePost($input: PostInput!) {
createPost(input: $input) {
id
title
}
}
`;
const invalidVariables = {
input: {
title: "", // Empty title should be invalid
content: "Content",
authorId: "999" // Non-existent author
}
};
const response = await request(app)
.post('/graphql')
.send({ query: invalidMutation, variables: invalidVariables })
.expect(200); // GraphQL returns 200 but with errors
expect(response.body).toHaveProperty('errors');
expect(response.body.errors.length).toBeGreaterThan(0);
});
});
@Test
public void testGraphQLUserQuery() {
String query = """
{
"query": "query GetUser($id: ID!) { user(id: $id) { id name email posts { id title createdAt } } }",
"variables": {
"id": "1"
}
}
""";
given()
.baseUri(BASE_URL)
.contentType(ContentType.JSON)
.body(query)
.when()
.post("/graphql")
.then()
.statusCode(200)
.body("data.user.id", equalTo("1"))
.body("data.user.name", notNullValue())
.body("data.user.posts", notNullValue())
.body("data.user.posts", hasSize(greaterThanOrEqualTo(0)));
}
@Test
public void testGraphQLMutation() {
String mutation = """
{
"query": "mutation CreatePost($input: PostInput!) { createPost(input: $input) { id title content author { id name } createdAt } }",
"variables": {
"input": {
"title": "Test Post",
"content": "This is a test post content",
"authorId": "1"
}
}
}
""";
given()
.baseUri(BASE_URL)
.contentType(ContentType.JSON)
.body(mutation)
.when()
.post("/graphql")
.then()
.statusCode(200)
.body("data.createPost.id", notNullValue())
.body("data.createPost.title", equalTo("Test Post"))
.body("data.createPost.author.id", equalTo("1"))
.body("data.createPost.createdAt", notNullValue());
}
@Test
public void testGraphQLErrorHandling() {
String invalidQuery = """
{
"query": "query GetUser($id: ID!) { user(id: $id) { id nonExistentField } }",
"variables": {
"id": "1"
}
}
""";
given()
.baseUri(BASE_URL)
.contentType(ContentType.JSON)
.body(invalidQuery)
.when()
.post("/graphql")
.then()
.statusCode(400)
.body("errors", hasSize(greaterThan(0)))
.body("errors[0].message", notNullValue());
}
@Test
public void testNestedGraphQLQuery() {
String nestedQuery = """
{
"query": "query GetUserWithPostsAndComments($userId: ID!) { user(id: $userId) { id name posts { id title comments { id content author { id name } } } } }",
"variables": {
"userId": "1"
}
}
""";
Response response = given()
.baseUri(BASE_URL)
.contentType(ContentType.JSON)
.body(nestedQuery)
.when()
.post("/graphql")
.then()
.statusCode(200)
.body("data.user", notNullValue())
.body("data.user.posts", notNullValue())
.extract().response();
// Additional validation for nested structures
JsonPath jsonPath = response.jsonPath();
List<Object> posts = jsonPath.getList("data.user.posts");
if (!posts.isEmpty()) {
assertThat(jsonPath.get("data.user.posts[0].comments")).isNotNull();
}
}
@Test
public void testGraphQLInputValidation() {
String invalidMutation = """
{
"query": "mutation CreatePost($input: PostInput!) { createPost(input: $input) { id title } }",
"variables": {
"input": {
"title": "",
"content": "Content",
"authorId": "999"
}
}
}
""";
given()
.baseUri(BASE_URL)
.contentType(ContentType.JSON)
.body(invalidMutation)
.when()
.post("/graphql")
.then()
.statusCode(200) // GraphQL returns 200 but with errors
.body("errors", hasSize(greaterThan(0)))
.body("errors[0].message", containsString("validation"));
}
@Test
public void testGraphQLFieldSelection() {
String selectiveQuery = """
{
"query": "query GetUserSelective($id: ID!) { user(id: $id) { name email } }",
"variables": {
"id": "1"
}
}
""";
Response response = given()
.baseUri(BASE_URL)
.contentType(ContentType.JSON)
.body(selectiveQuery)
.when()
.post("/graphql")
.then()
.