Who Is This Course For?
This course is for manual testers and junior Java developers who want to build a production-grade API test automation framework. You will learn every tool by name and command — and you will also understand exactly why each tool exists so you can adapt to any project on day one of a new job.
- Creating a Maven project in Eclipse — understanding
pom.xmlstructure - Adding core dependencies:
io.rest-assured:rest-assured,org.testng:testng,com.fasterxml.jackson.core:jackson-databind - Dependency scopes: why REST Assured uses
testscope - Running
mvn dependency:resolveto confirm setup
- HTTP methods:
GET(read),POST(create),PUT(replace),PATCH(partial update),DELETE(remove) - Request anatomy: URL, headers, body, query params, path params
- Response anatomy: status code, response headers, JSON body
- Status code groups — 2xx success, 4xx client errors, 5xx server errors
-
given()— builds the request: headers, body, params. Think of it as "given these conditions..." -
when()— fires the HTTP call:.get(url),.post(url),.put(url),.delete(url) -
then()— asserts the response: "...then I expect this result" - Full readable chain:
given().when().get("/repos").then().statusCode(200)
-
.and()— reads naturally between assertions; no functional difference -
.assertThat()— alternative tothen(), used by teams preferring assertion-first style - Why REST Assured offers these aliases: tests should read like sentences, not code
-
RequestSpecification— whatgiven()returns; you build the request on it -
Response— raw HTTP response object returned bywhen().get() -
ValidatableResponse— whatthen()returns; enables chained assertion methods
-
.statusCode(200)— assert exact success code for GET/PUT responses -
.statusCode(201)— assert "created" for POST;.statusCode(204)for DELETE (no body returned) -
.statusCode(anyOf(is(200), is(201)))— when multiple codes are valid
-
.header("Content-Type", containsString("json"))— confirm JSON response type -
.body("name", equalTo("myrepo"))— check a single field value inline -
.body("items.name", hasItems("repo1","repo2"))— verify list contents -
notNullValue(),greaterThan(0),containsString()— Hamcrest matchers toolkit - Chaining multiple
.body()calls in onethen()block — assert everything at once
-
.extract().jsonPath()— returns aJsonPathobject for field-level data extraction -
.extract().asString()— the raw JSON string if you need to inspect or log it - When to extract vs. when to assert inline — a practical decision guide
-
jsonPath.get("name")— single value extraction (auto-typed by Jackson) -
jsonPath.getString("login")/getInt("id")/getFloat("size")— explicit typed extraction -
jsonPath.getList("repos")— entire array as a Java List -
jsonPath.getList("repos", String.class)— typed list -
jsonPath.getJsonObject("owner")— nested object as a Map -
jsonPath.getList("repos").size()— count how many items came back
- Dot notation for nested data:
jsonPath.get("owner.login") - Array indexing:
jsonPath.get("repos[0].name") - Storing extracted IDs or names to reuse in the next API call
-
.pathParam("owner","octocat")used with URL template/repos/{owner}/{repo} -
.queryParam("page", 2)and.queryParam("per_page", 30)— pagination control - Chaining multiple
.queryParam()calls vs. passing aMapvia.queryParams(map)
-
.contentType(ContentType.JSON)— tells the server you are sending JSON -
.accept(ContentType.JSON)— tells the server what format you want back -
.header("Accept", "application/vnd.github.v3+json")— GitHub-specific version header -
.body("{\"name\":\"myrepo\"}")— raw JSON string body for quick tests -
.body(pojoObject)— POJO auto-serialised by Jackson; cleaner and type-safe - PUT vs PATCH difference: PUT replaces the full resource; PATCH updates only specified fields
-
.header("Authorization", "Bearer " + token)— the standard pattern for token-based auth - Generating a GitHub Personal Access Token (PAT) from GitHub Settings → Developer Settings
- Required scopes for our tests:
repo,delete_repo,admin:org - Why you store the token in a variable now and move it to a properties file later in the course
- Test with no token — expect
401 Unauthorized(not recognised) - Test with a valid token but insufficient scope — expect
403 Forbidden(recognised but not allowed) - Why testing both failure modes matters: they reveal different security gaps
- Base URL:
https://api.github.com— set once in BaseClass, used everywhere - GitHub API version header:
X-GitHub-Api-Version: 2022-11-28 - Rate limits: 5000 requests/hour with a token — how to stay within limits during testing
- Create:
POST /user/repos— returns 201 with full repo object - Read:
GET /repos/{owner}/{repo}— extract and assert name, visibility, owner - Update:
PATCH /repos/{owner}/{repo}— change name or description, assert the change - Delete:
DELETE /repos/{owner}/{repo}— expect 204 No Content; verify deletion with a follow-up GET that returns 404 - Complete E2E scenario: create → read → update → delete as four chained test methods
- POJO = a plain Java class whose fields mirror the JSON structure of a request or response
- Private fields + public getters/setters + no-argument constructor — the three requirements
- Request POJO (what you send) vs Response POJO (what you receive) as separate classes
-
@JsonProperty("html_url")— maps the JSON key name to your Java field name when they differ -
@JsonIgnoreProperties(ignoreUnknown = true)— prevents failure when the API adds new fields you didn't model -
@JsonInclude(Include.NON_NULL)— skip null fields when serialising, keeping the request body clean
- Serialisation:
.body(repoPojo)— REST Assured + Jackson converts the POJO to JSON automatically - Deserialisation:
.extract().as(RepoResponse.class)— JSON response mapped to a typed Java object - Asserting on POJO fields:
Assert.assertEquals(repo.getName(), "myrepo")— clean, IDE-autocompleted assertions
-
@BeforeClass— runs once before all tests in this file (used for: set base URI, read config, auth setup) -
@AfterClass— runs once after all tests finish (used for: final cleanup, teardown resources) -
@BeforeMethod— runs before every single test method (used for: creating fresh test data) -
@AfterMethod— runs after every single test (used for: logging pass/fail, deleting test data)
-
@AfterMethod public void tearDown(ITestResult result)— TestNG injects the result object automatically -
result.getStatus()— returnsITestResult.SUCCESS,FAILURE, orSKIP -
result.getName()— which test method just ran, for log messages - Why this matters: you can log "PASSED: createRepo" or "FAILED: deleteRepo" without extra assertions
- XML suite defines which classes and groups run — no need to run files individually
- Groups:
<include name="smoke"/>runs only the fast checks;<include name="regression"/>runs everything - Parallel execution:
parallel="methods"to speed up long suites
-
public class BaseClass— the shared parent that holds everything all tests need -
RestAssured.baseURI = "https://api.github.com"in@BeforeClass— set once, used by all -
protected static String TOKEN— loaded from properties file, available to all child classes - Why static: token does not change during the suite run, so one copy is enough
-
public class RepoTests extends BaseClass— inherits all setup automatically -
public class OrgTests extends BaseClass— same pattern, different test scope - No need to declare base URI or token in any test class — it just works
-
com.training.base→ BaseClass |com.training.tests→ all test classes -
com.training.pojo→ data objects |com.training.utils→ helpers -
src/test/resources→ testng.xml, properties files
-
.log().all()— full request detail (URL, headers, body) printed to console -
.log().ifValidationFails()— only logs when an assertion fails; keeps CI output clean -
RestAssured.enableLoggingOfRequestAndResponseIfValidationFails()— enables this globally in one line
-
ResponseSpecBuilder— define a reusable set of response checks (status + content type) -
builder.expectStatusCode(200).expectContentType(ContentType.JSON) -
ResponseSpecification spec = builder.build() - Apply per test:
then().spec(spec)— or globally:RestAssured.responseSpecification = spec - Why this matters: change the expected status code in one place and every test that uses the spec updates automatically
- Simple key=value text files:
base.url=https://api.github.com - Three environment files:
dev.properties,qa.properties,prod.properties - Stored in
src/test/resources/so Maven includes them automatically
-
Properties props = new Properties()— create a container -
props.load(new FileInputStream("src/test/resources/qa.properties"))— load the file -
props.getProperty("base.url")— read any value by key - Done in
BaseClass @BeforeClassso all child test classes get the value automatically
-
System.getProperty("env")reads the environment flag passed at runtime - Command:
mvn test -Denv=qa— no code change required - Conditional loading: read the env value first, then load the matching properties file
-
public class ApiHelper— centralises repetitive request patterns into named methods -
public Response get(String endpoint)— wraps the full given/header/when/get chain -
public Response post(String endpoint, Object body)— wraps POST with content type and body - Test method goes from 6 lines to 1 line per request
-
<T>— the Java generic type parameter, meaning "this method works with any type you tell it" -
public <T> T getAs(String endpoint, Class<T> type)— one method that returns any POJO - Calling it:
RepoResponse repo = helper.getAs("/repos/me/test", RepoResponse.class) - Why this is powerful: add 10 new POJOs without writing 10 new helper methods
- Don't Repeat Yourself — every pattern that appears in more than two places belongs in a utility
- Helper vs. test class responsibility: helpers make calls; tests assert outcomes
- How the complete picture looks: BaseClass → ApiHelper → TestClass → POJO
-
maven-surefire-plugin— the bridge between Maven and TestNG; it reads your testng.xml and runs the suite - Config in
pom.xml:<suiteXmlFiles><suiteXmlFile>testng.xml</suiteXmlFile> - Command:
mvn test— runs the full suite;mvn test -Denv=qa— runs against QA - Pinning plugin version: avoids unexpected test failures after Maven updates
- Maven reads
pom.xml→ Surefire readstestng.xml→ TestNG instantiates test classes → BaseClass@BeforeClassloads properties → REST Assured fires HTTP calls → results reported - HTML reports auto-generated in
target/surefire-reports/— shareable with the team
- Jenkins or GitHub Actions pipeline: add
mvn test -Denv=qaas a build step - Environment variables in CI replace local properties file values — no secrets in code
- Full framework folder review:
base/,tests/,pojo/,utils/,resources/ - Walking through the complete project in a mock interview — how to present this framework confidently
REST Assured Core Q&A
Walk through given().when().then(). Why use ResponseSpecBuilder? What is the difference between inline .body() assertion and .extract().jsonPath().get()? When would you choose each?
Framework Architecture Q&A
Why is RestAssured.baseURI set in @BeforeClass of BaseClass? How does inheritance eliminate repeated setup? What role does ITestResult play in @AfterMethod?
POJO & Environment Q&A
Explain @JsonIgnoreProperties(ignoreUnknown=true) in plain English. How do you make the same test suite run against dev and QA without changing code? How does System.getProperty() fit in?
Maven & CI Q&A
How does mvn test know which tests to run? What connects pom.xml to testng.xml? How would you configure a CI pipeline to run your suite automatically after every code push?