Serenity BDD REST API Testing: From Basics to Reports

Serenity BDD REST API Testing: From Basics to Reports

Serenity BDD includes built-in REST API testing support via the serenity-rest-assured module. It wraps REST Assured with Serenity's reporting engine, so API tests produce the same structured HTML reports as browser tests — step details, request/response logs, and requirements traceability.

Setup

<dependency>
    <groupId>net.serenity-bdd</groupId>
    <artifactId>serenity-rest-assured</artifactId>
    <version>4.1.20</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>net.serenity-bdd</groupId>
    <artifactId>serenity-junit5</artifactId>
    <version>4.1.20</version>
    <scope>test</scope>
</dependency>

Basic GET Request

import net.serenitybdd.rest.SerenityRest;
import net.thucydides.core.annotations.Step;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import net.serenitybdd.junit5.SerenityJUnit5Extension;

import static org.hamcrest.Matchers.*;

@ExtendWith(SerenityJUnit5Extension.class)
class UserApiTest {

    @Test
    void get_all_users_returns_list() {
        SerenityRest.given()
            .baseUri("https://api.example.com")
            .header("Accept", "application/json")
        .when()
            .get("/users")
        .then()
            .statusCode(200)
            .body("users", hasSize(greaterThan(0)))
            .body("users[0].id", notNullValue())
            .body("users[0].email", containsString("@"));
    }

    @Test
    void get_user_by_id_returns_correct_user() {
        SerenityRest.given()
            .baseUri("https://api.example.com")
            .pathParam("id", 42)
        .when()
            .get("/users/{id}")
        .then()
            .statusCode(200)
            .body("id", equalTo(42))
            .body("email", equalTo("alice@example.com"));
    }

    @Test
    void get_nonexistent_user_returns_404() {
        SerenityRest.given()
            .baseUri("https://api.example.com")
        .when()
            .get("/users/99999")
        .then()
            .statusCode(404)
            .body("error", equalTo("User not found"));
    }
}

SerenityRest is a drop-in replacement for REST Assured's RestAssured. Every request is logged in the Serenity report with request details and response body.

POST, PUT, DELETE

@Test
void create_user_returns_201() {
    String requestBody = """
        {
          "email": "newuser@example.com",
          "name": "New User",
          "role": "viewer"
        }
        """;

    SerenityRest.given()
        .baseUri("https://api.example.com")
        .header("Content-Type", "application/json")
        .body(requestBody)
    .when()
        .post("/users")
    .then()
        .statusCode(201)
        .header("Location", containsString("/users/"))
        .body("id", notNullValue())
        .body("email", equalTo("newuser@example.com"));
}

@Test
void update_user_modifies_name() {
    SerenityRest.given()
        .baseUri("https://api.example.com")
        .header("Content-Type", "application/json")
        .pathParam("id", 42)
        .body("{\"name\": \"Updated Name\"}")
    .when()
        .put("/users/{id}")
    .then()
        .statusCode(200)
        .body("name", equalTo("Updated Name"));
}

@Test
void delete_user_returns_204() {
    SerenityRest.given()
        .baseUri("https://api.example.com")
        .pathParam("id", 42)
    .when()
        .delete("/users/{id}")
    .then()
        .statusCode(204);
}

Step Libraries for API Tests

Wrap API calls in @Step methods for cleaner test code and better report output:

public class UserApiSteps {

    private static final String BASE_URL = "https://api.example.com";

    @Step("Get all users")
    public ValidatableResponse getAllUsers() {
        return SerenityRest.given()
            .baseUri(BASE_URL)
            .header("Accept", "application/json")
        .when()
            .get("/users")
        .then();
    }

    @Step("Get user with id {0}")
    public ValidatableResponse getUserById(int id) {
        return SerenityRest.given()
            .baseUri(BASE_URL)
            .pathParam("id", id)
        .when()
            .get("/users/{id}")
        .then();
    }

    @Step("Create user with email {0}")
    public ValidatableResponse createUser(String email, String name) {
        return SerenityRest.given()
            .baseUri(BASE_URL)
            .header("Content-Type", "application/json")
            .body(Map.of("email", email, "name", name))
        .when()
            .post("/users")
        .then();
    }

    @Step("Delete user {0}")
    public ValidatableResponse deleteUser(int id) {
        return SerenityRest.given()
            .baseUri(BASE_URL)
            .pathParam("id", id)
        .when()
            .delete("/users/{id}")
        .then();
    }
}
@ExtendWith(SerenityJUnit5Extension.class)
class UserApiTest {

    @Steps
    UserApiSteps api;

    @Test
    void create_and_retrieve_user() {
        // Create
        int userId = api.createUser("test@example.com", "Test User")
            .statusCode(201)
            .extract().path("id");

        // Retrieve
        api.getUserById(userId)
            .statusCode(200)
            .body("email", equalTo("test@example.com"));

        // Cleanup
        api.deleteUser(userId)
            .statusCode(204);
    }
}

Authentication

Bearer Token

SerenityRest.given()
    .baseUri("https://api.example.com")
    .header("Authorization", "Bearer " + getAuthToken())
    .when()
    .get("/protected/resource")
    .then()
    .statusCode(200);

Basic Auth

SerenityRest.given()
    .baseUri("https://api.example.com")
    .auth().basic("username", "password")
    .when()
    .get("/users")
    .then()
    .statusCode(200);

OAuth2

SerenityRest.given()
    .baseUri("https://api.example.com")
    .auth().oauth2(accessToken)
    .when()
    .get("/users/me")
    .then()
    .statusCode(200);

Combining API and Browser Tests

Serenity supports mixing API and browser tests in the same report. Use the API to set up test data, then browser tests to verify the UI:

@ExtendWith(SerenityJUnit5Extension.class)
class UserManagementTest {

    @Steps
    UserApiSteps api;

    @Managed
    WebDriver driver;

    @Test
    void new_user_appears_in_admin_ui() {
        // Create user via API
        int userId = api.createUser("newuser@example.com", "New User")
            .statusCode(201)
            .extract().path("id");

        // Verify in browser
        driver.get("https://example.com/admin/users");
        assertThat(driver.findElement(By.xpath("//td[text()='newuser@example.com']"))
            .isDisplayed()).isTrue();

        // Cleanup
        api.deleteUser(userId).statusCode(204);
    }
}

Both the API calls and browser interactions appear as steps in the same test report.

Response Extraction and Chaining

@Test
void create_order_and_verify_confirmation() {
    // Create order
    String orderId = SerenityRest.given()
        .baseUri("https://api.example.com")
        .header("Content-Type", "application/json")
        .body(orderPayload)
    .when()
        .post("/orders")
    .then()
        .statusCode(201)
        .extract().path("orderId");

    // Verify confirmation email was queued
    SerenityRest.given()
        .baseUri("https://api.example.com")
        .queryParam("orderId", orderId)
    .when()
        .get("/notifications/pending")
    .then()
        .statusCode(200)
        .body("notifications.orderId", hasItem(orderId));
}

Report Output

API test reports in Serenity show:

  • Request method, URL, headers, and body
  • Response status, headers, and body
  • Step names from @Step annotations
  • Assertions and whether they passed

This means a test failure includes the exact request that was made and the response that came back — no need to re-run with extra logging to diagnose.

Summary

serenity-rest-assured brings Serenity's reporting to API tests. Every request is logged in the HTML report with full request/response details. Combined with @Step-annotated step libraries and @Epic/@Feature annotations, API tests slot into the same requirements hierarchy as browser tests.

The main benefit over plain REST Assured is the report. If your team already uses Serenity for browser tests, using SerenityRest instead of RestAssured is a low-effort way to bring API tests into the same reporting structure.

Read more

Start now free