Q28 of 40 · REST Assured

What's your approach to handling test data setup/teardown in REST Assured?

REST AssuredSeniorrest-assuredtest-datasetup-teardownisolationapi-testing

Short answer

Short answer: Use @BeforeEach to POST test resources via REST Assured, capture their IDs, and clean up in @AfterEach via DELETE. For expensive shared data, scope to @BeforeAll with test-specific filtering. Every test should set up exactly what it needs and clean up after itself — no implicit dependency on prior test execution order.

Detail

Per-test setup/teardown — the default:

private int userId;

@BeforeEach
void createUser() {
    userId = given(reqSpec)
        .body(Map.of("name", "Alice", "email", "alice-" + UUID.randomUUID() + "@test.com"))
        .when().post("/users")
        .then().statusCode(201)
        .extract().path("id");
}

@AfterEach
void deleteUser() {
    given(reqSpec).when().delete("/users/" + userId).then().statusCode(204);
}

UUID prefix for unique emails: prevents "already exists" conflicts in parallel execution.

Suite-level shared data (@BeforeAll): for costly data that many tests read but don't modify (a large catalogue, reference data). Tag it clearly as read-only; any test that modifies it must set up its own copy.

Idempotent teardown: always use anyOf(is(204), is(404)) in teardown assertions — if setup failed mid-way, the resource may not exist, and a strict 204 assertion in teardown obscures the real failure.

Anti-patterns:

  • Tests that rely on data created by a previous test (order coupling)
  • Hardcoded IDs (userId = 1) that assume database state
  • No teardown — leaves dirty state that causes flakiness in the next run

// EXAMPLE

class OrderApiTest extends BaseApiTest {
    private int userId;
    private int orderId;

    @BeforeEach
    void setup() {
        // Each test gets its own user + order — isolated by UUID email
        userId = given(reqSpec)
            .body(Map.of("name", "TestUser", "email", "u-" + UUID.randomUUID() + "@test.com"))
            .when().post("/users")
            .then().statusCode(201).extract().<Integer>path("id");

        orderId = given(reqSpec)
            .body(Map.of("userId", userId, "items", List.of(Map.of("sku", "A1", "qty", 2))))
            .when().post("/orders")
            .then().statusCode(201).extract().<Integer>path("id");
    }

    @AfterEach
    void teardown() {
        // Idempotent — 204 if deleted, 404 if setup failed before resource was created
        given(reqSpec).when().delete("/orders/" + orderId)
            .then().statusCode(anyOf(is(204), is(404)));
        given(reqSpec).when().delete("/users/" + userId)
            .then().statusCode(anyOf(is(204), is(404)));
    }

    @Test
    void getOrder_returnsCorrectUserId() {
        given(reqSpec).when().get("/orders/" + orderId)
        .then().statusCode(200).body("userId", equalTo(userId));
    }
}

// WHAT INTERVIEWERS LOOK FOR

Per-test isolation with UUID-prefixed data, idempotent teardown with anyOf(204, 404), understanding the difference between per-test and per-suite scoping, and the explicit rejection of test-order dependency.

// COMMON PITFALL

Using a strict .statusCode(204) in teardown — if setup fails before the resource is created, teardown gets a 404 and reports a teardown failure that obscures the actual root cause.