Q31 of 40 · REST Assured
How would you test idempotency keys with REST Assured?
REST AssuredSeniorrest-assuredidempotencypaymentsapi-designtesting-patterns
Short answer
Short answer: Send the same request twice with identical X-Idempotency-Key headers and assert both responses are identical in status and payload. Then send a third request with a different key and verify a new resource is created. Verify the first key still returns the cached response, not the new resource.
Detail
Idempotency keys guarantee that retrying a request (e.g., after a network timeout) does not create duplicate side effects. Testing idempotency requires asserting three distinct behaviours:
- First call: creates the resource, returns 201.
- Retry with same key: returns the same 201 response (cached), does not create a second resource.
- New key: creates a new resource, returns a different ID.
String key = UUID.randomUUID().toString();
// First call — creates resource
int id1 = given(reqSpec).header("X-Idempotency-Key", key)
.body(paymentBody)
.when().post("/payments")
.then().statusCode(201)
.extract().path("id");
// Retry — same key, same response
int id2 = given(reqSpec).header("X-Idempotency-Key", key)
.body(paymentBody)
.when().post("/payments")
.then().statusCode(201)
.extract().path("id");
assertThat(id1).isEqualTo(id2); // idempotent — same resource
// New key — new resource
int id3 = given(reqSpec).header("X-Idempotency-Key", UUID.randomUUID().toString())
.body(paymentBody)
.when().post("/payments")
.then().statusCode(201)
.extract().path("id");
assertThat(id3).isNotEqualTo(id1); // different resource
Additional scenarios: same key with a different body (server should return 422 — key conflict); expired key (server may reject as key TTL has passed).
// EXAMPLE
@Test
void createPayment_withSameIdempotencyKey_returnsIdenticalResponse() {
String idempotencyKey = UUID.randomUUID().toString();
Map<String, Object> payload = Map.of(
"orderId", "ord-" + UUID.randomUUID(),
"amount", 49.99,
"currency","GBP"
);
// First call
String transactionId = given(reqSpec)
.header("X-Idempotency-Key", idempotencyKey)
.body(payload)
.when().post("/payments")
.then().statusCode(201)
.extract().path("transactionId");
// Retry — must return same transactionId
given(reqSpec)
.header("X-Idempotency-Key", idempotencyKey)
.body(payload)
.when().post("/payments")
.then()
.statusCode(201)
.body("transactionId", equalTo(transactionId));
// Verify only one payment exists for the original order
given(reqSpec).queryParam("orderId", payload.get("orderId"))
.when().get("/payments")
.then().statusCode(200)
.body("content.size()", equalTo(1));
}// WHAT INTERVIEWERS LOOK FOR
Testing all three scenarios (first call, retry with same key, new key), asserting the resource count to confirm no duplicate was created, and awareness of edge cases (same key, different body). This is a payment/fintech-common pattern.
// COMMON PITFALL
Only asserting that the second call returns 201 — this doesn't verify the response is identical (same transactionId) or that no duplicate resource was created in the database.