Allure Report with REST-assured: Document API Tests with Rich Reports

Allure Report with REST-assured: Document API Tests with Rich Reports

Allure integrates with REST-assured via a filter that captures every HTTP request and response and attaches them to the test report. When an API test fails, the Allure report shows the exact request sent and response received — no log digging required.

Key Takeaways

AllureRestAssured filter attaches request/response to the report. Add it as a request specification filter and every HTTP call is logged automatically.

RestAssured.filters(new AllureRestAssured()) enables global attachment. Or add it per-request for selective logging.

Request and response appear as attachments in the test step. Reviewers see the exact HTTP exchange — headers, body, status code.

Combine with @Step for step documentation. Use @Step annotations on test helper methods to show the logical test flow alongside the HTTP details.

@Issue and @TmsLink connect failures to Jira and TestOps. Failing tests link directly to the ticket.

Dependencies

<!-- pom.xml -->
<dependencies>
    <dependency>
        <groupId>io.rest-assured</groupId>
        <artifactId>rest-assured</artifactId>
        <version>5.4.0</version>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>io.qameta.allure</groupId>
        <artifactId>allure-rest-assured</artifactId>
        <version>2.27.0</version>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>io.qameta.allure</groupId>
        <artifactId>allure-junit5</artifactId>
        <version>2.27.0</version>
        <scope>test</scope>
    </dependency>
</dependencies>

Basic Setup

import io.qameta.allure.restassured.AllureRestAssured;
import io.restassured.RestAssured;
import io.restassured.specification.RequestSpecification;
import org.junit.jupiter.api.BeforeAll;

class BaseApiTest {

    @BeforeAll
    static void configureRestAssured() {
        RestAssured.baseURI = "https://api.example.com";
        RestAssured.filters(new AllureRestAssured()); // global filter
    }
}

Now every REST-assured call automatically attaches request and response to the Allure report.

Writing Annotated API Tests

import io.qameta.allure.*;
import org.junit.jupiter.api.Test;

@Epic("User Management API")
@Feature("User CRUD Operations")
class UserApiTest extends BaseApiTest {

    @Test
    @Story("Create User")
    @Severity(SeverityLevel.CRITICAL)
    @DisplayName("POST /users creates user and returns 201")
    void createUserReturns201() {
        given()
            .contentType("application/json")
            .body("""
                {
                    "name": "Alice Smith",
                    "email": "alice@example.com",
                    "role": "user"
                }
            """)
        .when()
            .post("/users")
        .then()
            .statusCode(201)
            .body("id", notNullValue())
            .body("name", equalTo("Alice Smith"))
            .body("email", equalTo("alice@example.com"));
    }

    @Test
    @Story("Get User")
    @Severity(SeverityLevel.NORMAL)
    @Issue("API-123")
    @TmsLink("TC-456")
    void getUserById() {
        // Create user first
        String userId = createUser("Bob Jones", "bob@example.com");

        // Retrieve and verify
        given()
            .pathParam("id", userId)
        .when()
            .get("/users/{id}")
        .then()
            .statusCode(200)
            .body("id", equalTo(userId))
            .body("name", equalTo("Bob Jones"));
    }

    @Test
    @Story("Delete User")
    @Severity(SeverityLevel.NORMAL)
    void deleteUserReturns404WhenFetched() {
        String userId = createUser("ToDelete", "delete@example.com");

        // Delete
        given()
            .pathParam("id", userId)
        .when()
            .delete("/users/{id}")
        .then()
            .statusCode(204);

        // Verify gone
        given()
            .pathParam("id", userId)
        .when()
            .get("/users/{id}")
        .then()
            .statusCode(404);
    }

    @Step("Create user with name={name} and email={email}")
    private String createUser(String name, String email) {
        return given()
            .contentType("application/json")
            .body(String.format("""{"name": "%s", "email": "%s"}""", name, email))
        .when()
            .post("/users")
        .then()
            .statusCode(201)
            .extract()
            .path("id");
    }
}

Customizing the Allure Filter

Control what gets logged:

RestAssured.filters(
    new AllureRestAssured()
        .setRequestTemplate("custom-request-template.ftl")  // custom format
        .setResponseTemplate("custom-response-template.ftl")
);

Or add the filter selectively per request:

// Global setup (most common)
RestAssured.filters(new AllureRestAssured());

// Per-request (when you want to control which calls are logged)
given()
    .filter(new AllureRestAssured())
    .contentType("application/json")
    // ...

Adding Manual Attachments

Attach custom data alongside the automatic request/response logs:

@Test
void paginatedListReturnsCorrectPage() {
    Response response = given()
        .queryParam("page", 2)
        .queryParam("limit", 10)
    .when()
        .get("/users")
    .then()
        .statusCode(200)
        .extract()
        .response();

    // Attach the full response for reference
    Allure.addAttachment("User List Response",
        "application/json",
        response.getBody().asString(),
        ".json");

    // Verify pagination
    assertThat(response.jsonPath().getList("items").size()).isEqualTo(10);
    assertThat(response.jsonPath().getInt("pagination.page")).isEqualTo(2);
    assertThat(response.jsonPath().getBoolean("pagination.hasMore")).isTrue();
}

Using @Step for Flow Documentation

Steps make the logical test flow visible in the report, separate from the HTTP exchange details:

@Epic("Order API")
@Feature("Order Lifecycle")
class OrderApiTest extends BaseApiTest {

    @Test
    @Story("Complete Order Flow")
    @Severity(SeverityLevel.BLOCKER)
    void completeOrderFlowFromCartToConfirmation() {
        // Each @Step method appears as a step in the Allure report
        String cartId = createCart();
        addItemToCart(cartId, "WIDGET-001", 2);
        addItemToCart(cartId, "GADGET-002", 1);
        String orderId = checkoutCart(cartId, "credit_card");
        verifyOrderStatus(orderId, "CONFIRMED");
    }

    @Step("Create empty cart")
    private String createCart() {
        return given()
            .when().post("/carts")
            .then().statusCode(201)
            .extract().path("cartId");
    }

    @Step("Add {quantity}x {sku} to cart {cartId}")
    private void addItemToCart(String cartId, String sku, int quantity) {
        given()
            .pathParam("cartId", cartId)
            .body(Map.of("sku", sku, "quantity", quantity))
        .when()
            .post("/carts/{cartId}/items")
        .then()
            .statusCode(200);
    }

    @Step("Checkout cart {cartId} with {paymentMethod}")
    private String checkoutCart(String cartId, String paymentMethod) {
        return given()
            .pathParam("cartId", cartId)
            .body(Map.of("paymentMethod", paymentMethod))
        .when()
            .post("/carts/{cartId}/checkout")
        .then()
            .statusCode(201)
            .extract().path("orderId");
    }

    @Step("Verify order {orderId} has status {expectedStatus}")
    private void verifyOrderStatus(String orderId, String expectedStatus) {
        given()
            .pathParam("orderId", orderId)
        .when()
            .get("/orders/{orderId}")
        .then()
            .statusCode(200)
            .body("status", equalTo(expectedStatus));
    }
}

The Allure report for this test shows:

  1. Step: "Create empty cart" + HTTP: POST /carts → 201
  2. Step: "Add 2x WIDGET-001 to cart cart-123" + HTTP: POST /carts/cart-123/items → 200
  3. Step: "Add 1x GADGET-002 to cart cart-123" + HTTP: POST /carts/cart-123/items → 200
  4. Step: "Checkout cart cart-123 with credit_card" + HTTP: POST /carts/cart-123/checkout → 201
  5. Step: "Verify order ord-789 has status CONFIRMED" + HTTP: GET /orders/ord-789 → 200

Running and Generating Reports

# Run tests
mvn test

# Generate report
mvn allure:report

# Open report
mvn allure:serve

Or with allure CLI:

allure generate target/allure-results --clean -o target/allure-report
allure open target/allure-report

The combination of REST-assured for fluent API testing and Allure for rich reporting is standard practice for Java API test suites. Every failure is self-documenting — the exact HTTP exchange is in the report.

Read more

Start now free