Now accepting guest editorial from SAP, Oracle, Workday and ServiceNow practitioners.Pitch a guide
The Sign-Off Table

Lesson 4 of 6

Writes, ETags and concurrency

10 min

Listen to this lesson

0:00 / 7:12

A write test has to prove more than "it saved". It has to prove the right entity changed in the right way, nothing else changed, and that two people changing the same document cannot silently overwrite each other. OData handles the last point with ETags: a version stamp the server gives you with each entity and checks when you write.

The write verbs

VerbUseTypical successTester's check
POSTCreate an entity (or call a function import / action)201 Created with the new entityGenerated key returned; read it back
PATCH / MERGEChange some properties204 No ContentRead back: changed fields changed, others unchanged
PUTReplace the whole entity204 No ContentFields you omitted may be reset; avoid unless intended
DELETEDelete an entity, where the service allows it204 No ContentRead back returns 404

Write verbs and what to verify after each.

SAP services often allow deep inserts: one POST that creates a header with its items, for example a sales order with to_Item in API_SALES_ORDER_SRV. Many SAP APIs do not allow every verb on every entity: check the metadata annotations, and test that a forbidden verb is refused cleanly (often 405 or a business error), not silently ignored.

How ETags work

  1. GET the entity. The ETag comes back in the ETag response header and in the body: __metadata.etag in V2 JSON, @odata.etag in V4.
  2. Send the PATCH, PUT or DELETE with If-Match: <etag>, plus the CSRF token.
  3. If nobody changed the entity since your read, the server applies the change and issues a new ETag.
  4. If someone did, the server answers 412 Precondition Failed. Re-read, re-apply your change to the current data, and try again.
  5. If you send no If-Match to a service that requires it, the server answers 428 Precondition Required.
Optimistic locking: the stale ETag is refused with 412, so Maria's change is not silently overwritten.
The Sign-Off Table · thesignofftable.com/academy/sap-tst-211

Some clients send If-Match: * to make the error go away. An SAP KBA gives that as a workaround for one API, and it does make the call succeed, because * matches any version. It also switches off the protection. Use it only when overwriting is the documented intent, never as the default in a test suite.

The outcomes of an ETag-protected write and how a test or client recovers.
The Sign-Off Table · thesignofftable.com/academy/sap-tst-211

Worked example: changing an address in CMQ

  1. Fetch the CSRF token with a GET on the service root and keep the cookies.
  2. GET the address of business partner 17100001 that Wei's recipe created, and store its ETag and current street value as the baseline.
  3. PATCH the street with If-Match set to the stored ETag. Expect 204.
  4. GET it again. Expect the new street, the other fields unchanged and a new ETag.
  5. PATCH again with the old ETag. Expect 412, and the street from step 3 still in place.
  6. Restore the baseline street with the current ETag. Expect 204, then read back to confirm.
  1. POST headerA_SalesOrder with sold-to 17100001 and sales area 1710 / 10 / 00
  2. Nested itemsto_Item with CMC-ALT-100, quantity 50, in the same body
  3. 201 CreatedResponse returns the new order number and its items
  4. Read backCheck pricing, schedule lines and the document flow in the system
  5. Clean upReject or delete per the data recipe
A deep insert creates the header and its items in one POST; the read-back proves what the system derived.
The Sign-Off Table · thesignofftable.com/academy/sap-tst-211

Some operations are not plain create or update at all: releasing a document, posting, or setting a status. V2 services expose them as function imports, usually called with POST and parameters in the URL; V4 services expose actions, often bound to an entity. They need the CSRF token, and many also need the ETag. Test them like any write: known start state, expected status change, read-back and cleanup.

Designing concurrency tests

TestStepsExpected
Happy pathGET, PATCH with current ETag, GET204; value changed; new ETag
Stale ETagGET (v1), change in another session, PATCH with v1412; value from the other session kept
Missing preconditionPATCH without If-Match428 where the service requires ETags
Read-only fieldPATCH a property the metadata marks not updatableError or ignored, as documented; record which
CleanupRestore the original value with its current ETagData back to the recorded baseline

A minimal concurrency test set for one updatable entity.

A useful habit: after every write, compare the full entity before and after, not just the field you changed. SAP backends derive values. Changing a ship-to address can redetermine a tax jurisdiction; changing a sales order quantity re-prices the item and can change the schedule lines and the credit status. The API call returned 204, but the business result is in those derived fields, and that is what Maria will notice in order-to-cash testing.

What would you do?

An interface from Contoso's CRM updates business partner search terms through the API. In CMQ testing it sometimes overwrites changes that key users made minutes earlier in Fiori. The interface code sends If-Match: * on every call. What should Priya ask for?

Key points
  • POST creates (201), PATCH/MERGE and PUT update (204), DELETE removes (204); always read back.
  • ETags come in the ETag header and in __metadata.etag (V2) or @odata.etag (V4).
  • If-Match with a stale ETag gives 412; a missing If-Match gives 428 where required.
  • If-Match: * disables the protection; avoid it as a default.

Check your understanding · not graded

Lesson check

  1. 1. What does 412 Precondition Failed mean on a PATCH?

  2. 2. Where does a V4 response carry the ETag in the body?

  3. 3. Why avoid If-Match: * as a default?

  4. 4. What should follow every write in a test?