An OOP-driven REST API test automation framework for the restful-booker demo API, built with Java 25, REST Assured, JUnit 5, Lombok, Jackson and Allure.
./gradlew testConfig (base URI, auth credentials, timeout) lives in
src/test/resources/config.properties and can be overridden per-run with
matching -D system properties, e.g.:
./gradlew test -Dbase.uri=https://staging.example.com./gradlew test allureReport
open build/reports/allure-report/allureReport/index.htmlOr, to launch a local server serving the report directly:
./gradlew allureServeEvery HTTP call is captured by the AllureRestAssured filter, so each test
step in the report has the full request/response (headers, body, status)
attached.
src/test/java/com/booker/api/
├── config/ typed, externalized environment config (Singleton)
├── client/ HTTP transport + resource clients (Composition)
├── auth/ auth token acquisition/caching
├── model/ request/response POJOs (Builder)
├── data/ test data factories (Factory)
├── base/ shared JUnit lifecycle (Inheritance)
└── tests/ the actual test classes, grouped by resource
ApiClient(encapsulation) — the only class that touches REST Assured'sgiven(). It owns the sharedRequestSpecification(base URI, JSON content type, aLocalDate-aware Jackson mapper, the Allure reporting filter) and exposes plainget/post/put/patch/deletemethods returningResponse.BookingApiClient/AuthApiClient(composition over inheritance) — each has-aApiClientfield rather than extending a shared base client. Their only job is mapping domain operations (createBooking,deleteBooking, ...) onto HTTP calls. There is no generic base-client class to force an artificialis-arelationship where none exists.BaseTest(inheritance, used deliberately) — the one genuineis-arelationship in the framework. Every test class extends it to share a single@BeforeAllthat wires the whole client graph, avoiding boilerplate duplication across test classes.Booking/BookingDates/AuthRequest(builder pattern) — Lombok@Builder @Jacksonizedmodels replace hand-written JSON text blocks with fluent, typed construction, e.g.Booking.builder().firstname("Jim")...build().BookingDataFactory(factory pattern) — centralizes what a valid vs. invalid booking payload means for this suite (validBooking(),missingFirstnameBooking(),negativeTotalPriceBooking()), built on top of the model builders. The factory owns intent; the builder owns assembly.ConfigProvider(singleton) — loadsconfig.propertiesonce behind a private constructor and static holder, exposing an immutableEnvironmentConfigrecord. Nothing else in the framework parses properties/env vars directly.AuthTokenManager— lazily fetches and caches the/authtoken for the whole suite, since every authenticated write (PUT/PATCH/DELETE) can reuse it instead of re-authenticating per test.
All client methods return the raw REST Assured Response, not deserialized
POJOs — deserialization happens in the test itself (response.as(Booking Response.class)) for positive cases, while negative cases assert directly on
response.getStatusCode() without any deserialization exception getting in
the way.
| Class | Covers |
|---|---|
PingTest |
GET /ping liveness check |
BookingRetrievalTest |
list ids, filter by name, get by id, invalid id → 404 |
BookingLifecycleTest |
full create → get → PUT → PATCH → delete → verify-gone flow |
BookingCreationValidationTest |
valid create; missing required field; negative price |
BookingAuthorizationTest |
PUT/PATCH/DELETE rejected (403) without/with an invalid token |
Negative-path expectations (e.g. a missing firstname returning 500, a
negative totalprice being accepted rather than rejected) were verified
against the live API before being asserted, rather than assumed.
Every test class that creates bookings deletes them again in @AfterEach, to
keep the shared public demo API's data tidy across runs.
.github/workflows/ci.yml runs ./gradlew test on every push/PR and uploads
the Allure results as a build artifact.