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:
- Step: "Create empty cart" + HTTP: POST /carts → 201
- Step: "Add 2x WIDGET-001 to cart cart-123" + HTTP: POST /carts/cart-123/items → 200
- Step: "Add 1x GADGET-002 to cart cart-123" + HTTP: POST /carts/cart-123/items → 200
- Step: "Checkout cart cart-123 with credit_card" + HTTP: POST /carts/cart-123/checkout → 201
- 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:serveOr with allure CLI:
allure generate target/allure-results --clean -o target/allure-report
allure open target/allure-reportThe 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.