EML
RAP / CDS / ODataadvanced

EML Part 5: Testing RAP with EML and Transactional Consistency

Key takeaway

Use EML to prove that a RAP business object behaves correctly and consistently: test structure, isolating tests, checking permissions and messages, testing draft and bulk behavior, and simulation for pre-checks.

Explanation

📌 KEY POINTS
✅ Test validations with COMMIT ENTITIES — they run only at save.

✅ Isolate with SQL test doubles; ROLLBACK ENTITIES in teardown.

✅ Cover draft, feature control (GET PERMISSIONS) and bulk.

💡 Run tests in ATC / transport release checks.

▶ WHERE WE ARE
You can use EML fluently (Parts 1–4). The last step is using it to verify behavior automatically. Behavior Implementations Part 5 introduced unit tests; this lesson goes further into what to test and how to keep tests reliable.

━━━━━━━━━━━━━━━━━━━━
▶ 1. WHY EML IS THE RIGHT TEST INTERFACE

OData, Fiori and other BOs all end up in the same RAP runtime that EML uses. A test that creates, changes and commits via EML exercises the real behavior — authorization, feature control, determinations, validations, draft — without a UI. Tests are fast, repeatable and run in CI.

━━━━━━━━━━━━━━━━━━━━
▶ 2. A GOOD TEST STRUCTURE

  • Arrange: prepare input with %cid; set up test doubles for data the BO reads (e.g. supplier master data).
  • Act: one EML operation (or a short sequence) — MODIFY, EXECUTE, COMMIT ENTITIES RESPONSE.
  • Assert: FAILED and %fail-cause, REPORTED messages (severity, number, %element), and data read back with READ ENTITIES.
  • Teardown: ROLLBACK ENTITIES.

One behavior per test method; descriptive names like "approve_rejected_in_status_new".

━━━━━━━━━━━━━━━━━━━━
▶ 3. WHAT TO TEST — A CHECKLIST

  • Each validation: one passing and one failing case, including the message and field.
  • Each determination: result correct; idempotent (trigger twice, same result).
  • Each action: allowed and disallowed states; result returned; messages.
  • Feature control: GET PERMISSIONS returns the expected enabled/disabled values per status.
  • Authorization: run with a test user or use test doubles for authority checks where your release supports it.
  • Draft: create a draft (%is_draft = mk-on), change it, execute Prepare and Activate; verify the active instance.
  • Bulk: 100–500 instances in one call — no failures, acceptable runtime.
  • Cross-BO: calls to other BOs are replaced by test doubles, so you test only your own logic.

━━━━━━━━━━━━━━━━━━━━
▶ 4. ISOLATION

  • Tests whose save succeeds write to the database. Use the ABAP SQL test double framework (cl_osql_test_environment) to redirect your tables and CDS views to test data.
  • For other BOs your code calls, use RAP BO test doubles where your release provides them, or wrap the call in an interface you can replace in tests.
  • Never depend on data that happens to exist in the development system.

━━━━━━━━━━━━━━━━━━━━
▶ 5. CONSISTENCY CHECKS WITHOUT SAVING

  • COMMIT ENTITIES IN SIMULATION MODE (current releases) runs the save-sequence checks without persisting — useful in tests and in "check only" program runs.
  • A determine action (e.g. checkRequest) lets you run validations on demand via EXECUTE without committing.

━━━━━━━━━━━━━━━━━━━━
▶ 6. RUN THEM ALWAYS

Run ABAP Unit with ATC in the transport release check or in CI (gCTS / pipelines), so a failing test blocks the transport.

━━━━━━━━━━━━━━━━━━━━
▶ STEP BY STEP

  1. Take the checklist and write one test per item for the Purchase Request.
  2. Add the SQL test double for ZPR_HEAD and ZPR_ITEM.
  3. Add a draft test and a bulk test.
  4. Include the test class in your ATC check variant.

━━━━━━━━━━━━━━━━━━━━
▶ WHAT YOU HAVE AFTER THE SERIES

EML as your API, your integration tool and your test harness. Next: Managed RAP builds a complete BO with everything learned so far.

Code example

ABAP Code
CLASS ltc_purchreq_behavior DEFINITION FINAL FOR TESTING  DURATION SHORT RISK LEVEL HARMLESS.  PRIVATE SECTION.    CLASS-DATA sql_env TYPE REF TO if_osql_test_environment.    CLASS-METHODS class_setup.    CLASS-METHODS class_teardown.    METHODS teardown.     METHODS approve_disabled_in_status_new FOR TESTING.    METHODS total_is_idempotent            FOR TESTING.    METHODS draft_create_and_activate      FOR TESTING.ENDCLASS. CLASS ltc_purchreq_behavior IMPLEMENTATION.   METHOD class_setup.    " redirect BO tables to test doubles - no real data touched    sql_env = cl_osql_test_environment=>create(                i_dependency_list = VALUE #( ( 'ZPR_HEAD' ) ( 'ZPR_ITEM' ) ) ).  ENDMETHOD.   METHOD approve_disabled_in_status_new.    MODIFY ENTITIES OF zr_purchreq      ENTITY PurchaseRequest CREATE FIELDS ( Description CurrencyCode )      WITH VALUE #( ( %cid = 'R1' Description = 'T' CurrencyCode = 'EUR' ) )      MAPPED DATA(mapped).     GET PERMISSIONS ONLY INSTANCE FEATURES ENTITY zr_purchreq      FROM VALUE #( ( %tky = mapped-purchaserequest[ 1 ]-%tky ) )      REQUEST VALUE #( %action-approve = if_abap_behv=>mk-on )      RESULT DATA(perm)      FAILED DATA(perm_failed)      REPORTED DATA(perm_reported).     cl_abap_unit_assert=>assert_equals(      exp = if_abap_behv=>fc-o-disabled      act = perm-instances[ 1 ]-%action-approve ).  ENDMETHOD.   METHOD total_is_idempotent.    " create request + item, then touch the item twice: total must stay equal    " (arrange/act omitted for brevity - same deep create as EML Part 2)    " READ ... FIELDS ( TotalAmount ) -> total1; UPDATE item Quantity same value;    " READ again -> total2; assert_equals( exp = total1 act = total2 ).  ENDMETHOD.   METHOD draft_create_and_activate.    MODIFY ENTITIES OF zr_purchreq      ENTITY PurchaseRequest        CREATE FIELDS ( Description CurrencyCode )        WITH VALUE #( ( %cid = 'D1' %is_draft = if_abap_behv=>mk-on                        Description = 'Draft test' CurrencyCode = 'EUR' ) )      MAPPED DATA(mapped).     MODIFY ENTITIES OF zr_purchreq      ENTITY PurchaseRequest        EXECUTE Activate FROM VALUE #( ( %key = mapped-purchaserequest[ 1 ]-%key ) )      FAILED DATA(act_failed).     COMMIT ENTITIES RESPONSE OF zr_purchreq FAILED DATA(commit_failed).    cl_abap_unit_assert=>assert_initial( act = act_failed ).    cl_abap_unit_assert=>assert_initial( act = commit_failed ).  ENDMETHOD.   METHOD teardown.    ROLLBACK ENTITIES.    sql_env->clear_doubles( ).  ENDMETHOD.   METHOD class_teardown.    sql_env->destroy( ).  ENDMETHOD. ENDCLASS. " Pre-check without saving (current releases):" COMMIT ENTITIES IN SIMULATION MODE RESPONSE OF zr_purchreq FAILED DATA(sim_failed).

Real project scenario

A team discovered a validation that only failed during save because it checked the combination of header and items — but their tests only called MODIFY and never COMMIT ENTITIES, so the validation never ran in tests. Bad data was caught only by users in QAS.
They reworked the test class: every validation test now ends with COMMIT ENTITIES RESPONSE, uses SQL test doubles so successful saves do not pollute the system, and includes draft and bulk cases. The test suite runs in the transport release check. Since then, validation regressions have been caught before transport.

Common mistakes

  • Tests that only MODIFY and never commit, so validations are never exercised.
  • Tests depending on existing data in the development system.
  • No teardown with ROLLBACK ENTITIES, so tests influence each other.
  • Testing draft only through the UI.
  • No bulk test.
  • Tests that call real foreign BOs instead of doubles, failing for reasons outside your code.

Best practices

  • Arrange–Act–Assert with EML; commit when save-phase rules matter.
  • Isolate with SQL test doubles and BO test doubles.
  • Cover validations, determinations (idempotency), actions, feature control, draft and bulk.
  • Use simulation mode or determine actions for check-only runs.
  • Run tests in ATC and the transport release check.

Interview angle

Questions: "How do you test a RAP BO?", "How do you make sure validations are tested?", "How do you isolate tests from database data?", "How do you test draft?"
Strong answer: EML-based ABAP Unit tests with MODIFY, COMMIT ENTITIES RESPONSE (validations run only at save), assertions on FAILED/REPORTED/READ results, ROLLBACK ENTITIES in teardown; SQL test doubles (cl_osql_test_environment) and BO test doubles for isolation; draft tested with %is_draft and Activate; feature control via GET PERMISSIONS; bulk test for performance; tests run in ATC/transport checks.

Practice this lesson

Quick check

1. A unit test for a validation only calls MODIFY ENTITIES and passes, although the validation is broken. Why?

2. A unit test modifies an instance with invalid data but the validation never fails. What is missing in the test?

3. How do you keep EML unit tests isolated from real data?

Interview questions

1. Design a test suite for a RAP business object before go-live. What must it cover?

Answer it in your own words first, then compare.

2. Why must EML-based tests of validations call COMMIT ENTITIES, and how do you keep such tests isolated?

Answer it in your own words first, then compare.

Practice more with Interview AI →

Reference-verified: the steps are checked against official SAP documentation and tutorials, and object and field names in the sample code are checked for consistency. The code has not been compiled or run on an SAP system by ERPClimb. Syntax marked release-dependent varies by SAP release, so check it in your own system.

More lessons in this topic

Interview questions on this topic

Related SAP topics