On this page10 sections
REST Assured — Build an API Test Suite
Write a REST Assured + Java test suite for a public API: CRUD operations, JSON schema validation, authentication, and chained request scenarios using TestNG.
Role
Java automation engineer
Difficulty
IntermediateTime limit
90–120 min
Category
api testing
REST AssuredJava 17TestNGMavenJSON Schema Validator
Scenario
You have joined a QA team that uses Java and REST Assured for all API automation. Your first task is to build a test suite for the Restful-Booker API (https://restful-booker.herokuapp.com), a real hosted hotel-booking API widely used for API testing practice. The suite must cover the full booking lifecycle, authentication, JSON schema validation, and negative paths — and must be structured so a colleague can read, extend, and run it from a fresh clone without asking you any questions.
Requirements
- 1.Implement a shared RequestSpecification in a SpecBuilder utility class so all tests share the same base URI, Content-Type, and Accept headers without duplication
- 2.Authenticate via POST /auth to obtain a token and attach it as a Cookie header on all mutating requests (PUT, PATCH, DELETE)
- 3.Test all CRUD operations on /booking: create (POST), read by id (GET), list all (GET), full update (PUT), partial update (PATCH), and delete (DELETE)
- 4.Validate at least two response bodies against JSON Schema files loaded from src/test/resources/schemas — do not rely on field-level assertions alone for contract coverage
- 5.Write a chained scenario: POST /booking → capture the returned id → GET /booking/{id} and assert the data matches the created payload → DELETE /booking/{id} → GET /booking/{id} and verify 404
- 6.Cover at least 3 negative paths: GET with an invalid booking ID (404), DELETE without an auth token (403), and POST with a missing required field (500 — document the actual Restful-Booker behaviour)
- 7.Use TestNG groups to tag tests as 'smoke' (critical-path GET + auth) and 'regression' (full CRUD + negative paths), and declare them in a testng.xml suite file
Starter data
- ›API base URL: https://restful-booker.herokuapp.com
- ›Auth endpoint: POST /auth — body: {"username": "admin", "password": "password123"} — returns {"token": "<value>"}
- ›Create booking: POST /booking — required fields: firstname (string), lastname (string), totalprice (integer), depositpaid (boolean), bookingdates: {checkin: YYYY-MM-DD, checkout: YYYY-MM-DD}; optional: additionalneeds (string)
- ›Restful-Booker note: DELETE /booking/{id} returns 201 (not 204) — this is a known quirk; assert 201 and document it rather than treating it as a defect
- ›Restful-Booker note: POST /auth returns 200 even for bad credentials; the error appears in the body as {"reason": "Bad credentials"} — assert on the body, not only the status code
- ›Maven dependencies to add: io.rest-assured:rest-assured (5.x), io.rest-assured:json-schema-validator, org.testng:testng (7.x), com.fasterxml.jackson.core:jackson-databind
Expected deliverables
- ✓A runnable Maven project: pom.xml with all dependencies declared, no hardcoded credentials in any Java file
- ✓SpecBuilder.java with at least a base RequestSpecification and an authenticated variant that adds the Cookie token
- ✓At least two POJO classes (e.g. Booking, BookingDates) used for Jackson serialisation and deserialisation
- ✓At least one JSON Schema file in src/test/resources/schemas/ used in a REST Assured assertion
- ✓TestNG test classes covering: auth, full CRUD, chained lifecycle scenario, and negative paths
- ✓A testng.xml suite file that separates smoke and regression groups and can be run via `mvn test -DsuiteXmlFile=testng.xml`
- ✓Terminal output or a saved Surefire report showing all expected tests passing
- ✓A brief README.md (5–10 lines) explaining how to set credentials and run the suite
Evaluation rubric
| Dimension | What reviewers look for |
|---|---|
| RequestSpecification design | Is the base URI, Content-Type, and Accept header set in exactly one place? Is there a separate authenticated spec that adds the Cookie token without duplicating the base config? Tests that set headers inline on every request will score lower. |
| Auth integration | Is the token obtained once (e.g. in @BeforeSuite) and stored for reuse — not fetched in every test that needs it? Is it attached to mutating requests only? Is the negative case (missing token → 403) actually tested and the assertion specific? |
| JSON schema validation | Are JSON Schema files used to validate response structure beyond field-level assertions? Do the schemas define field types (string, integer, boolean) and required fields? Are the schema files stored in src/test/resources/schemas and loaded via classpath — not inlined as strings? |
| Chained scenario completeness | Does the chain test the full lifecycle end-to-end? Does it verify the resource is gone after deletion (GET returns 404)? Is the created id captured from the POST response and used in subsequent steps rather than hardcoded? |
| Negative path accuracy | Are error scenarios chosen deliberately (missing auth, invalid ID, malformed body) rather than repeating happy-path variants? Are both the status code and the error response body asserted? Are Restful-Booker quirks (201 on DELETE, 200 on bad auth) documented rather than treated as failures? |
| Code organisation | Are test classes, service/helper classes, and POJOs in separate packages? Could a colleague navigate the project without a walkthrough? Is there any duplicated setup code in test classes that should be in a shared base class or @BeforeSuite? |
Sample solution outline
- ›SpecBuilder.getBaseSpec() → RequestSpecification with baseUri, contentType(JSON), accept(JSON)
- ›SpecBuilder.getAuthSpec(token) → extends base spec with .cookie("token", token)
- ›AuthTest.testValidAuth() → POST /auth {admin, password123} → 200, body has 'token' field that is a non-empty string
- ›AuthTest.testInvalidAuth() → POST /auth {admin, wrongpass} → 200, body has 'reason: Bad credentials', no token field
- ›BookingCrudTest.createBooking() → POST /booking → 200, response matches booking JSON schema, capture id
- ›BookingCrudTest.getBookingById() → GET /booking/{id} → 200, response matches schema, firstname equals created value
- ›BookingCrudTest.updateBooking() → PUT /booking/{id} with auth spec → 200, totalprice field reflects updated value
- ›BookingCrudTest.partialUpdateBooking() → PATCH /booking/{id} with auth spec → 200, only changed field is updated
- ›BookingCrudTest.deleteBooking() → DELETE /booking/{id} with auth spec → 201 (Restful-Booker quirk — documented)
- ›BookingCrudTest.verifyDeleted() → GET /booking/{id} → 404
- ›BookingNegativeTest.getInvalidId() → GET /booking/99999 → 404
- ›BookingNegativeTest.deleteWithoutAuth() → DELETE /booking/{id} without Cookie → 403
- ›BookingNegativeTest.createMissingField() → POST /booking with no totalprice → 500 (Restful-Booker behaviour — documented as expected, not a test defect)
Common mistakes
- Setting base URI and Content-Type in every test method instead of in a shared RequestSpecification — all tests break when the base URL changes and the fix requires editing dozens of lines
- Calling POST /auth inside every test that needs the token — authentication should run once in @BeforeSuite and the token stored as a reusable static field
- Asserting only the status code ('got a 200') without checking that the response body has the correct structure — schema validation is the difference between a status test and a contract test
- Skipping the DELETE-then-GET verification step — proving the resource is gone is as important as the delete itself, and is the most commonly missed part of a CRUD chain
- Hardcoding a booking ID directly in a GET or DELETE call (e.g. `/booking/1`) instead of using the id returned by the POST — breaks when the API resets or another test creates data first
- Treating Restful-Booker's 201 response on DELETE as a failure rather than documenting it as the API's known behaviour
- Placing all test methods in a single file — a single 15-method BookingTest.java is harder to maintain and target with TestNG groups than separate AuthTest, CrudTest, and NegativeTest classes
Submission checklist
- pom.xml compiles cleanly with `mvn test-compile` and all dependencies resolve without errors
- SpecBuilder (or equivalent) centralises base URI, headers, and auth configuration in one place
- At least one JSON Schema file in src/test/resources/schemas/ is used in a REST Assured `body(matchesJsonSchemaInClasspath(...))` assertion
- All five CRUD verbs are covered: POST (create), GET by id, GET all, PUT or PATCH (update), DELETE
- Chained scenario: create → read → update → delete → verify 404
- At least 3 negative path tests with asserted status codes and response body fields
- TestNG groups (smoke, regression) are declared and testng.xml separates them
- `mvn test` exits 0 (all tests pass) or all failures are documented as known API quirks
- No hardcoded credentials appear in any .java source file
Extension ideas
- +Add an ExtentReports listener so each run produces a rich HTML report with the request/response body embedded per test for easy debugging
- +Parameterise booking creation with TestNG @DataProvider to run the same create-and-validate test with 5 different valid payload variants in a single test method
- +Add a response-time assertion using REST Assured's `time()` extractor: assert that all GET /booking requests complete within 2000ms to catch latency regressions
- +Write a contract test for the GET /booking (list) response: validate that every id in the returned array is an integer using Hamcrest's `everyItem(instanceOf(Integer.class))`