Q34 of 40 · REST Assured

How do you avoid flakiness in REST Assured tests that depend on shared state?

REST AssuredSeniorrest-assuredflakinesstest-isolationreliabilityapi-testing

Short answer

Short answer: Root causes: shared mutable data (test A creates; test B deletes before A reads), time-dependent assertions, and implicit ordering. Fix with per-test data setup/teardown using UUID-prefixed resources, Awaitility for eventual-consistency endpoints, and explicit test independence so any test can run alone or in any order.

Detail

Root-cause analysis is the first step — categorise the flakiness before applying a fix:

Category 1 — Shared mutable test data: tests share a user/order/product; one test modifies or deletes it while another reads it. Fix: per-test isolation with unique resources created in @BeforeEach.

Category 2 — Eventual consistency: the API returns 201 but the data isn't readable yet (async indexing, cache propagation). Fix: Awaitility polling instead of an immediate GET.

Category 3 — Test order coupling: test B assumes test A ran first and left data behind. Fix: each test sets up its own data; run the suite in random order with @TestMethodOrder(MethodOrderer.Random.class) to catch hidden dependencies.

Category 4 — External service availability: tests that hit a real external API will flake when that API is slow or down. Fix: WireMock stubs for non-deterministic external dependencies.

Category 5 — Token expiry: a token fetched at suite start expires mid-run. Fix: the OAuth2Filter refresh pattern.

Flakiness tracking: quarantine persistently flaky tests with a @Tag("flaky") annotation and run them separately; measure the flake rate over a week before root-causing.

// EXAMPLE

// FLAKY — two tests sharing the same user ID
class FlakyUserTest {
    @Test void updateUser_changesName() {
        given(reqSpec).body(Map.of("name", "Bob"))
            .when().put("/users/42").then().statusCode(200);
    }
    @Test void getUser_returnsAlice() {        // flakes if updateUser runs first
        given(reqSpec).when().get("/users/42")
            .then().body("name", equalTo("Alice"));
    }
}

// STABLE — each test creates and owns its data
class StableUserTest extends BaseApiTest {
    private int userId;

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

    @AfterEach void cleanup() {
        given(reqSpec).when().delete("/users/" + userId)
            .then().statusCode(anyOf(is(204), is(404)));
    }

    @Test void updateUser_changesName() {
        given(reqSpec).body(Map.of("name", "Bob"))
            .when().put("/users/" + userId).then().statusCode(200);
    }

    @Test void getUser_returnsAlice() {
        given(reqSpec).when().get("/users/" + userId)
            .then().body("name", equalTo("Alice"));
    }
}

// WHAT INTERVIEWERS LOOK FOR

Categorising flakiness by root cause (shared data, eventual consistency, order coupling, external deps, token expiry) and matching the right fix to each. Random-order test execution as a detection tool is a strong signal.

// COMMON PITFALL

Adding Thread.sleep() to fix flakiness caused by eventual consistency. This masks the symptom, makes tests slower, and still fails on slower environments. Awaitility is the correct fix.