REST Assured Tutorial

Master API Testing in Java with Hands-On Examples

00

What is REST Assured?

REST Assured is a powerful Java library that simplifies testing and validating REST APIs. It brings the simplicity of dynamic languages into Java, making API testing intuitive and readable.

🚀 Simple & Readable

Write tests in a BDD (Behavior-Driven Development) style that reads like plain English

🔧 Powerful

Support for all HTTP methods, authentication, headers, cookies, file uploads, and more

✅ Validation

Built-in matchers and assertions for comprehensive response validation

🔌 Integration

Works seamlessly with TestNG, JUnit, Maven, and CI/CD pipelines

💡 Why Use REST Assured?

Instead of writing verbose HTTP client code, REST Assured lets you write clean, expressive tests that focus on what you're testing, not how to make HTTP requests. It's the industry standard for API testing in Java.

01

Maven Setup

Add REST Assured to your project by including these dependencies in your pom.xml:

pom.xml
<dependencies>
    <!-- REST Assured -->
    <dependency>
        <groupId>io.rest-assured</groupId>
        <artifactId>rest-assured</artifactId>
        <version>5.3.2</version>
        <scope>test</scope>
    </dependency>

    <!-- JSON Schema Validation -->
    <dependency>
        <groupId>io.rest-assured</groupId>
        <artifactId>json-schema-validator</artifactId>
        <version>5.3.2</version>
        <scope>test</scope>
    </dependency>

    <!-- TestNG or JUnit -->
    <dependency>
        <groupId>org.testng</groupId>
        <artifactId>testng</artifactId>
        <version>7.8.0</version>
        <scope>test</scope>
    </dependency>
</dependencies>

✅ Import Statements

For all examples in this tutorial, you'll need these static imports:

Java Imports
import static io.restassured.RestAssured.*;
import static io.restassured.matcher.RestAssuredMatchers.*;
import static org.hamcrest.Matchers.*;
import io.restassured.response.Response;
02

Your First Request - GET

Let's start with a simple GET request. REST Assured uses a fluent API with a Given-When-Then structure:

Basic GET Request
@Test
public void testGetSingleUser() {
    given()
        .baseUri("https://jsonplaceholder.typicode.com")
    .when()
        .get("/users/1")
    .then()
        .statusCode(200)
        .body("name", equalTo("Leanne Graham"))
        .body("email", equalTo("Sincere@april.biz"));
}

📖 Understanding the Structure

  • given() - Setup: specify base URI, headers, parameters, authentication
  • when() - Action: make the HTTP request (GET, POST, PUT, DELETE)
  • then() - Validation: assert response status, body, headers

With Query Parameters

GET with Query Parameters
@Test
public void testGetUsersWithParams() {
    given()
        .baseUri("https://jsonplaceholder.typicode.com")
        .queryParam("_page", 1)
        .queryParam("_limit", 5)
    .when()
        .get("/users")
    .then()
        .statusCode(200)
        .body("size()", equalTo(5));
}

Extract and Print Response

Extract Response
@Test
public void testExtractResponse() {
    Response response = given()
        .baseUri("https://jsonplaceholder.typicode.com")
    .when()
        .get("/users/1")
    .then()
        .statusCode(200)
        .extract()
        .response();
    
    // Print response
    System.out.println("Response Body: " + response.asString());
    
    // Extract specific values
    String name = response.path("name");
    String email = response.path("email");
    
    System.out.println("Name: " + name);
    System.out.println("Email: " + email);
}
03

POST Requests - Creating Data

POST requests send data to create new resources. You can send JSON, XML, or form data.

JSON Body as String

POST with JSON String
@Test
public void testCreateUser() {
    String requestBody = """
        {
            "name": "Jane Doe",
            "email": "jane@example.com",
            "age": 28
        }
        """;
    
    given()
        .baseUri("https://jsonplaceholder.typicode.com")
        .header("Content-Type", "application/json")
        .body(requestBody)
    .when()
        .post("/users")
    .then()
        .statusCode(201)
        .body("name", equalTo("Jane Doe"))
        .body("email", equalTo("jane@example.com"));
}

Using POJO (Plain Old Java Object)

User POJO Class
public class User {
    private String name;
    private String email;
    private int age;
    
    // Constructors
    public User() {}
    
    public User(String name, String email, int age) {
        this.name = name;
        this.email = email;
        this.age = age;
    }
    
    // Getters and Setters
    public String getName() { return name; }
    public void setName(String name) { this.name = name; }
    
    public String getEmail() { return email; }
    public void setEmail(String email) { this.email = email; }
    
    public int getAge() { return age; }
    public void setAge(int age) { this.age = age; }
}
POST with POJO
@Test
public void testCreateUserWithPOJO() {
    User user = new User("John Smith", "john@example.com", 32);
    
    given()
        .baseUri("https://jsonplaceholder.typicode.com")
        .contentType("application/json")
        .body(user)
    .when()
        .post("/users")
    .then()
        .statusCode(201)
        .body("name", equalTo("John Smith"));
}

✅ Automatic Serialization

REST Assured automatically converts POJOs to JSON using Jackson. No manual conversion needed!

04

PUT & PATCH - Updating Data

PUT replaces the entire resource, while PATCH updates specific fields.

PUT Request (Full Update)

PUT Request
@Test
public void testUpdateUser() {
    String updatedUser = """
        {
            "id": 1,
            "name": "Updated Name",
            "email": "updated@example.com",
            "age": 30
        }
        """;
    
    given()
        .baseUri("https://jsonplaceholder.typicode.com")
        .contentType("application/json")
        .body(updatedUser)
    .when()
        .put("/users/1")
    .then()
        .statusCode(200)
        .body("name", equalTo("Updated Name"));
}

PATCH Request (Partial Update)

PATCH Request
@Test
public void testPatchUser() {
    String partialUpdate = """
        {
            "email": "newemail@example.com"
        }
        """;
    
    given()
        .baseUri("https://jsonplaceholder.typicode.com")
        .contentType("application/json")
        .body(partialUpdate)
    .when()
        .patch("/users/1")
    .then()
        .statusCode(200)
        .body("email", equalTo("newemail@example.com"));
}
05

DELETE Requests

DELETE requests remove resources from the server.

DELETE Request
@Test
public void testDeleteUser() {
    given()
        .baseUri("https://jsonplaceholder.typicode.com")
    .when()
        .delete("/users/1")
    .then()
        .statusCode(200);  // JSONPlaceholder returns 200, many APIs return 204
}

⚠️ Common Status Codes for DELETE

  • 200 OK - Deleted successfully with response body
  • 204 No Content - Deleted successfully without response body
  • 404 Not Found - Resource doesn't exist
06

Response Validation

REST Assured provides powerful validation capabilities using Hamcrest matchers.

Status Code Validation

Status Code Checks
@Test
public void testStatusCodes() {
    // Exact status code
    given()
        .baseUri("https://jsonplaceholder.typicode.com")
    .when()
        .get("/users/1")
    .then()
        .statusCode(200);
    
    // Status code in range
    given()
        .baseUri("https://jsonplaceholder.typicode.com")
    .when()
        .post("/users")
    .then()
        .statusCode(is(oneOf(200, 201)));
}

Header Validation

Header Validation
@Test
public void testHeaders() {
    given()
        .baseUri("https://jsonplaceholder.typicode.com")
    .when()
        .get("/users/1")
    .then()
        .header("Content-Type", containsString("application/json"))
        .header("Server", notNullValue());
}

JSON Body Validation

Body Validation
@Test
public void testBodyValidation() {
    given()
        .baseUri("https://jsonplaceholder.typicode.com")
    .when()
        .get("/users/1")
    .then()
        .statusCode(200)
        // Single field validation
        .body("name", equalTo("Leanne Graham"))
        .body("id", is(1))
        .body("email", containsString("@"))
        
        // Nested field validation
        .body("address.city", equalTo("Gwenborough"))
        .body("address.geo.lat", equalTo("-37.3159"))
        
        // Multiple validations
        .body("username", notNullValue())
        .body("phone", not(emptyString()));
}

Collection Validation

Array/List Validation
@Test
public void testCollectionValidation() {
    given()
        .baseUri("https://jsonplaceholder.typicode.com")
    .when()
        .get("/users")
    .then()
        .statusCode(200)
        // Array size
        .body("size()", equalTo(10))
        
        // Check if array contains
        .body("name", hasItem("Leanne Graham"))
        
        // All items match condition
        .body("id", everyItem(greaterThan(0)))
        
        // Specific index
        .body("[0].name", equalTo("Leanne Graham"))
        .body("[0].id", is(1))
        
        // Find and validate
        .body("find { it.id == 1 }.name", equalTo("Leanne Graham"));
}

Response Time Validation

Performance Testing
@Test
public void testResponseTime() {
    given()
        .baseUri("https://jsonplaceholder.typicode.com")
    .when()
        .get("/users/1")
    .then()
        .time(lessThan(2000L));  // Less than 2 seconds
}
07

Authentication

REST Assured supports various authentication mechanisms.

Basic Authentication
@Test
public void testBasicAuth() {
    given()
        .baseUri("https://httpbin.org")
        .auth()
        .basic("username", "password")
    .when()
        .get("/basic-auth/username/password")
    .then()
        .statusCode(200)
        .body("authenticated", equalTo(true));
}
Bearer Token Authentication
@Test
public void testBearerToken() {
    String token = "your-jwt-token-here";
    
    given()
        .baseUri("https://api.example.com")
        .header("Authorization", "Bearer " + token)
    .when()
        .get("/protected-resource")
    .then()
        .statusCode(200);
}

// Alternative method
@Test
public void testBearerTokenAlt() {
    given()
        .baseUri("https://api.example.com")
        .auth()
        .oauth2("your-jwt-token-here")
    .when()
        .get("/protected-resource")
    .then()
        .statusCode(200);
}
OAuth 2.0
@Test
public void testOAuth2() {
    // Using pre-obtained access token
    String accessToken = "your-access-token";
    
    given()
        .baseUri("https://api.example.com")
        .auth()
        .oauth2(accessToken)
    .when()
        .get("/user/profile")
    .then()
        .statusCode(200);
}
API Key Authentication
@Test
public void testApiKeyInHeader() {
    given()
        .baseUri("https://api.example.com")
        .header("X-API-Key", "your-api-key-here")
    .when()
        .get("/data")
    .then()
        .statusCode(200);
}

@Test
public void testApiKeyInQueryParam() {
    given()
        .baseUri("https://api.example.com")
        .queryParam("api_key", "your-api-key-here")
    .when()
        .get("/data")
    .then()
        .statusCode(200);
}
08

Headers & Cookies

Working with Headers

Request Headers
@Test
public void testHeaders() {
    given()
        .baseUri("https://jsonplaceholder.typicode.com")
        // Single header
        .header("Content-Type", "application/json")
        
        // Multiple headers
        .header("Accept", "application/json")
        .header("User-Agent", "RestAssured-Tests")
        
        // Or using headers() for multiple
        .headers("Accept", "application/json", 
                 "User-Agent", "RestAssured-Tests")
    .when()
        .get("/users/1")
    .then()
        .statusCode(200);
}

Extract Response Headers

Response Headers
@Test
public void testExtractHeaders() {
    Response response = given()
        .baseUri("https://jsonplaceholder.typicode.com")
    .when()
        .get("/users/1")
    .then()
        .extract()
        .response();
    
    String contentType = response.getHeader("Content-Type");
    String server = response.getHeader("Server");
    
    System.out.println("Content-Type: " + contentType);
    System.out.println("Server: " + server);
}

Working with Cookies

Cookies
@Test
public void testCookies() {
    // Send cookie with request
    given()
        .baseUri("https://httpbin.org")
        .cookie("session_id", "abc123xyz")
    .when()
        .get("/cookies")
    .then()
        .statusCode(200)
        .body("cookies.session_id", equalTo("abc123xyz"));
}

@Test
public void testExtractCookies() {
    Response response = given()
        .baseUri("https://httpbin.org")
    .when()
        .get("/cookies/set?name=value")
    .then()
        .extract()
        .response();
    
    String cookieValue = response.getCookie("name");
    System.out.println("Cookie value: " + cookieValue);
}
09

JSON Path Expressions

Extract and validate complex JSON structures using JSON Path.

JSON Path Examples
@Test
public void testJsonPath() {
    given()
        .baseUri("https://jsonplaceholder.typicode.com")
    .when()
        .get("/users")
    .then()
        .statusCode(200)
        
        // Root level array size
        .body("$", hasSize(10))
        
        // Get all names
        .body("name", hasSize(10))
        
        // Get first user's name
        .body("[0].name", equalTo("Leanne Graham"))
        
        // Filter: Find user with specific ID
        .body("find { it.id == 5 }.name", equalTo("Chelsey Dietrich"))
        
        // findAll: Get all users with id > 5
        .body("findAll { it.id > 5 }.name", 
              hasItems("Mrs. Dennis Schulist", "Kurtis Weissnat"))
        
        // Nested path
        .body("[0].address.city", equalTo("Gwenborough"))
        
        // Multiple nested levels
        .body("[0].address.geo.lat", equalTo("-37.3159"))
        
        // Collect: Get all emails
        .body("collect { it.email }", 
              hasItem("Sincere@april.biz"));
}

📖 Common JSON Path Operators

Operator Description Example
$ Root element $ or $.users
. Child element user.name
[n] Array index users[0]
[*] All elements users[*].name
find Find first match find { it.id == 1 }
findAll Find all matches findAll { it.age > 18 }
10

Request & Response Specifications

Reuse common configurations across multiple tests using specifications.

Request Specification
import io.restassured.specification.RequestSpecification;
import io.restassured.specification.ResponseSpecification;
import io.restassured.builder.RequestSpecBuilder;
import io.restassured.builder.ResponseSpecBuilder;

public class APITests {
    
    private RequestSpecification requestSpec;
    private ResponseSpecification responseSpec;
    
    @BeforeClass
    public void setup() {
        // Build Request Specification
        requestSpec = new RequestSpecBuilder()
            .setBaseUri("https://jsonplaceholder.typicode.com")
            .setContentType("application/json")
            .addHeader("Accept", "application/json")
            .build();
        
        // Build Response Specification
        responseSpec = new ResponseSpecBuilder()
            .expectStatusCode(200)
            .expectContentType("application/json")
            .expectResponseTime(lessThan(3000L))
            .build();
    }
    
    @Test
    public void testWithSpec() {
        given()
            .spec(requestSpec)
        .when()
            .get("/users/1")
        .then()
            .spec(responseSpec)
            .body("name", equalTo("Leanne Graham"));
    }
    
    @Test
    public void testAnotherEndpoint() {
        given()
            .spec(requestSpec)
        .when()
            .get("/users/2")
        .then()
            .spec(responseSpec)
            .body("name", equalTo("Ervin Howell"));
    }
}

✅ Benefits of Specifications

  • DRY (Don't Repeat Yourself) - Define common settings once
  • Maintainability - Change base URI in one place
  • Consistency - All tests use same headers and validations
  • Readability - Tests focus on what's being tested
11

File Upload & Download

Upload Files

File Upload
import java.io.File;

@Test
public void testFileUpload() {
    File file = new File("src/test/resources/test-file.txt");
    
    given()
        .baseUri("https://httpbin.org")
        .multiPart("file", file)
    .when()
        .post("/post")
    .then()
        .statusCode(200)
        .body("files", notNullValue());
}

@Test
public void testMultipleFilesUpload() {
    File file1 = new File("src/test/resources/file1.txt");
    File file2 = new File("src/test/resources/file2.txt");
    
    given()
        .baseUri("https://httpbin.org")
        .multiPart("file1", file1)
        .multiPart("file2", file2)
    .when()
        .post("/post")
    .then()
        .statusCode(200);
}

Download Files

File Download
@Test
public void testFileDownload() {
    byte[] fileData = given()
        .baseUri("https://httpbin.org")
    .when()
        .get("/image/png")
    .then()
        .statusCode(200)
        .extract()
        .asByteArray();
    
    // Save to file
    try {
        Files.write(Paths.get("downloaded-image.png"), fileData);
        System.out.println("File downloaded successfully");
    } catch (IOException e) {
        e.printStackTrace();
    }
}
12

JSON Schema Validation

Validate entire JSON structure against a schema to ensure API contract compliance.

user-schema.json
{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "type": "object",
  "properties": {
    "id": {
      "type": "integer"
    },
    "name": {
      "type": "string"
    },
    "email": {
      "type": "string",
      "format": "email"
    },
    "address": {
      "type": "object",
      "properties": {
        "street": { "type": "string" },
        "city": { "type": "string" }
      },
      "required": ["street", "city"]
    }
  },
  "required": ["id", "name", "email"]
}
Schema Validation Test
import static io.restassured.module.jsv.JsonSchemaValidator.*;

@Test
public void testJsonSchemaValidation() {
    given()
        .baseUri("https://jsonplaceholder.typicode.com")
    .when()
        .get("/users/1")
    .then()
        .statusCode(200)
        .body(matchesJsonSchemaInClasspath("user-schema.json"));
}

@Test
public void testArraySchemaValidation() {
    given()
        .baseUri("https://jsonplaceholder.typicode.com")
    .when()
        .get("/users")
    .then()
        .statusCode(200)
        .body(matchesJsonSchemaInClasspath("users-array-schema.json"));
}

💡 Why Use Schema Validation?

  • Ensures API contract is followed correctly
  • Validates data types, required fields, and formats
  • Catches breaking changes early
  • Single assertion validates entire structure
13

Logging & Debugging

REST Assured provides comprehensive logging options for debugging.

Logging Options
@Test
public void testLogging() {
    // Log everything
    given()
        .log().all()
        .baseUri("https://jsonplaceholder.typicode.com")
    .when()
        .get("/users/1")
    .then()
        .log().all()
        .statusCode(200);
    
    // Log only if validation fails
    given()
        .baseUri("https://jsonplaceholder.typicode.com")
    .when()
        .get("/users/1")
    .then()
        .log().ifValidationFails()
        .statusCode(200);
    
    // Log only request/response body
    given()
        .log().body()
        .baseUri("https://jsonplaceholder.typicode.com")
    .when()
        .get("/users/1")
    .then()
        .log().body()
        .statusCode(200);
    
    // Log only headers
    given()
        .log().headers()
        .baseUri("https://jsonplaceholder.typicode.com")
    .when()
        .get("/users/1")
    .then()
        .log().headers()
        .statusCode(200);
    
    // Log only status line
    given()
        .baseUri("https://jsonplaceholder.typicode.com")
    .when()
        .get("/users/1")
    .then()
        .log().status()
        .statusCode(200);
}
Method Description
log().all() Log everything (request/response)
log().body() Log only the body
log().headers() Log only headers
log().cookies() Log only cookies
log().status() Log only status line
log().ifValidationFails() Log only when test fails
log().ifError() Log only if status code >= 400
14

TestNG Integration & Data Providers

Complete Test Class Example

UserAPITests.java
import org.testng.annotations.*;
import io.restassured.specification.RequestSpecification;
import static io.restassured.RestAssured.*;
import static org.hamcrest.Matchers.*;

public class UserAPITests {
    
    private RequestSpecification requestSpec;
    
    @BeforeClass
    public void setup() {
        baseURI = "https://jsonplaceholder.typicode.com";
        
        requestSpec = given()
            .contentType("application/json")
            .accept("application/json");
    }
    
    @Test(priority = 1)
    public void testGetAllUsers() {
        given()
            .spec(requestSpec)
        .when()
            .get("/users")
        .then()
            .statusCode(200)
            .body("$", hasSize(10));
    }
    
    @Test(priority = 2)
    public void testGetUserById() {
        given()
            .spec(requestSpec)
        .when()
            .get("/users/1")
        .then()
            .statusCode(200)
            .body("id", equalTo(1))
            .body("name", notNullValue());
    }
    
    @Test(priority = 3, dataProvider = "userIds")
    public void testMultipleUsers(int userId, String expectedName) {
        given()
            .spec(requestSpec)
        .when()
            .get("/users/" + userId)
        .then()
            .statusCode(200)
            .body("name", equalTo(expectedName));
    }
    
    @DataProvider(name = "userIds")
    public Object[][] getUserData() {
        return new Object[][] {
            {1, "Leanne Graham"},
            {2, "Ervin Howell"},
            {3, "Clementine Bauch"}
        };
    }
    
    @Test(priority = 4)
    public void testCreateUser() {
        String newUser = """
            {
                "name": "Test User",
                "email": "test@example.com"
            }
            """;
        
        given()
            .spec(requestSpec)
            .body(newUser)
        .when()
            .post("/users")
        .then()
            .statusCode(201)
            .body("name", equalTo("Test User"));
    }
    
    @AfterClass
    public void tearDown() {
        System.out.println("All tests completed");
    }
}

Data-Driven Testing

Data Provider Example
public class DataDrivenTests {
    
    @Test(dataProvider = "searchData")
    public void testSearchWithDifferentParams(String query, int expectedResults) {
        given()
            .baseUri("https://jsonplaceholder.typicode.com")
            .queryParam("userId", query)
        .when()
            .get("/posts")
        .then()
            .statusCode(200)
            .body("size()", equalTo(expectedResults));
    }
    
    @DataProvider(name = "searchData")
    public Object[][] getSearchData() {
        return new Object[][] {
            {"1", 10},
            {"2", 10},
            {"3", 10}
        };
    }
    
    // Reading from CSV or Excel
    @DataProvider(name = "usersFromFile")
    public Object[][] getUsersFromFile() throws IOException {
        // Read from CSV file
        List data = new ArrayList<>();
        BufferedReader br = new BufferedReader(
            new FileReader("src/test/resources/users.csv")
        );
        String line;
        while ((line = br.readLine()) != null) {
            String[] values = line.split(",");
            data.add(values);
        }
        br.close();
        return data.toArray(new Object[0][]);
    }
}
15

Best Practices

1. Use Specifications

Create reusable RequestSpecification and ResponseSpecification to avoid code duplication.

2. Separate Test Data

Keep test data in separate files (JSON, CSV, Excel) or use DataProviders for maintainability.

3. Use POJOs

Create Java classes for request/response bodies instead of string manipulation.

4. Validate Schema

Use JSON Schema validation to ensure API contracts are maintained.

5. Log Strategically

Use log().ifValidationFails() to reduce noise but capture failures.

6. Organize Tests

Group related tests in classes, use meaningful test names, and set priorities.

7. Environment Config

Externalize base URIs and credentials using properties files or environment variables.

8. Assert Wisely

Use appropriate Hamcrest matchers for clear, readable assertions.

Configuration File Example

config.properties
base.uri=https://jsonplaceholder.typicode.com
api.timeout=3000
auth.token=your-token-here
ConfigReader.java
import java.io.FileInputStream;
import java.util.Properties;

public class ConfigReader {
    private static Properties properties;
    
    static {
        try {
            properties = new Properties();
            FileInputStream fis = new FileInputStream(
                "src/test/resources/config.properties"
            );
            properties.load(fis);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    
    public static String getProperty(String key) {
        return properties.getProperty(key);
    }
}

// Usage in tests
@BeforeClass
public void setup() {
    RestAssured.baseURI = ConfigReader.getProperty("base.uri");
}
16

Complete Working Example

A full test suite demonstrating all major concepts:

CompleteAPITestSuite.java
import org.testng.annotations.*;
import io.restassured.response.Response;
import io.restassured.specification.RequestSpecification;
import io.restassured.builder.RequestSpecBuilder;
import static io.restassured.RestAssured.*;
import static org.hamcrest.Matchers.*;

public class CompleteAPITestSuite {
    
    private RequestSpecification requestSpec;
    private int createdUserId;
    
    @BeforeClass
    public void setupTestSuite() {
        // Set base URI
        baseURI = "https://jsonplaceholder.typicode.com";
        
        // Build request specification
        requestSpec = new RequestSpecBuilder()
            .setContentType("application/json")
            .addHeader("Accept", "application/json")
            .build();
        
        System.out.println("Test Suite Started");
    }
    
    @Test(priority = 1, description = "Verify API health")
    public void testAPIHealth() {
        given()
            .spec(requestSpec)
        .when()
            .get("/users")
        .then()
            .statusCode(200)
            .time(lessThan(3000L))
            .body("$", hasSize(greaterThan(0)));
    }
    
    @Test(priority = 2, description = "Get all users and validate response")
    public void testGetAllUsers() {
        Response response = given()
            .spec(requestSpec)
        .when()
            .get("/users")
        .then()
            .statusCode(200)
            .body("size()", equalTo(10))
            .body("name", everyItem(notNullValue()))
            .body("email", everyItem(containsString("@")))
            .extract()
            .response();
        
        System.out.println("Total users: " + response.jsonPath().getList("$").size());
    }
    
    @Test(priority = 3, description = "Get user by ID with validation")
    public void testGetUserById() {
        given()
            .spec(requestSpec)
            .pathParam("id", 1)
        .when()
            .get("/users/{id}")
        .then()
            .statusCode(200)
            .body("id", equalTo(1))
            .body("name", equalTo("Leanne Graham"))
            .body("email", equalTo("Sincere@april.biz"))
            .body("address.city", equalTo("Gwenborough"))
            .body("address.geo.lat", equalTo("-37.3159"));
    }
    
    @Test(priority = 4, description = "Create new user")
    public void testCreateUser() {
        String newUser = """
            {
                "name": "Automation Test User",
                "username": "testuser",
                "email": "testuser@test.com",
                "address": {
                    "street": "Test Street",
                    "city": "Test City",
                    "zipcode": "12345"
                }
            }
            """;
        
        Response response = given()
            .spec(requestSpec)
            .body(newUser)
        .when()
            .post("/users")
        .then()
            .statusCode(201)
            .body("name", equalTo("Automation Test User"))
            .body("email", equalTo("testuser@test.com"))
            .extract()
            .response();
        
        createdUserId = response.path("id");
        System.out.println("Created user with ID: " + createdUserId);
    }
    
    @Test(priority = 5, dependsOnMethods = "testCreateUser", 
          description = "Update user")
    public void testUpdateUser() {
        String updatedUser = """
            {
                "id": %d,
                "name": "Updated User Name",
                "email": "updated@test.com"
            }
            """.formatted(createdUserId);
        
        given()
            .spec(requestSpec)
            .pathParam("id", createdUserId)
            .body(updatedUser)
        .when()
            .put("/users/{id}")
        .then()
            .statusCode(200)
            .body("name", equalTo("Updated User Name"))
            .body("email", equalTo("updated@test.com"));
    }
    
    @Test(priority = 6, description = "Search posts by user ID")
    public void testSearchPosts() {
        given()
            .spec(requestSpec)
            .queryParam("userId", 1)
        .when()
            .get("/posts")
        .then()
            .statusCode(200)
            .body("size()", equalTo(10))
            .body("userId", everyItem(equalTo(1)))
            .body("[0].title", notNullValue());
    }
    
    @Test(priority = 7, description = "Verify pagination")
    public void testPagination() {
        given()
            .spec(requestSpec)
            .queryParam("_page", 1)
            .queryParam("_limit", 5)
        .when()
            .get("/posts")
        .then()
            .statusCode(200)
            .body("size()", equalTo(5));
    }
    
    @Test(priority = 8, description = "Test error handling")
    public void testNotFound() {
        given()
            .spec(requestSpec)
        .when()
            .get("/users/999999")
        .then()
            .statusCode(404);
    }
    
    @Test(priority = 9, dependsOnMethods = "testCreateUser",
          description = "Delete user")
    public void testDeleteUser() {
        given()
            .spec(requestSpec)
            .pathParam("id", createdUserId)
        .when()
            .delete("/users/{id}")
        .then()
            .statusCode(200);
        
        System.out.println("Deleted user with ID: " + createdUserId);
    }
    
    @Test(priority = 10, description = "Validate response time")
    public void testResponseTime() {
        long startTime = System.currentTimeMillis();
        
        given()
            .spec(requestSpec)
        .when()
            .get("/users")
        .then()
            .time(lessThan(2000L));
        
        long endTime = System.currentTimeMillis();
        System.out.println("Response time: " + (endTime - startTime) + "ms");
    }
    
    @AfterClass
    public void tearDown() {
        System.out.println("Test Suite Completed");
    }
}

✅ This Example Demonstrates:

  • Request specifications for code reuse
  • All HTTP methods (GET, POST, PUT, DELETE)
  • Query parameters and path parameters
  • Response validation with Hamcrest matchers
  • JSON path navigation
  • Test dependencies and priorities
  • Response extraction and logging
  • Error handling and edge cases
  • Performance testing (response time)