~/abap.dev — code, crafted in SAP
florin @ s4hana : ~/abap/posts/rap-unit-testing $ cat README.md

Unit Testing RAP Behavior with ABAP Mock Framework

The built-in ABAP mock framework lets you test your behavior implementations without a real database commit. Full walkthrough with a sales order scenario.

Why Mock RAP?

Testing RAP behavior classes the naive way — just calling MODIFY ENTITIES and checking the DB — has serious drawbacks:

  • Tests pollute the database and require cleanup
  • They run slowly (network + DB round-trip)
  • They can’t run in parallel without key collisions

The ABAP Mock Framework solves this by providing an in-memory implementation of the EML (Entity Manipulation Language) interface, so your handler methods run against a fake buffer, not the real DB.

Setup

CLASS ltc_sales_order DEFINITION FINAL
  FOR TESTING RISK LEVEL HARMLESS DURATION SHORT.

  PRIVATE SECTION.
    DATA mo_cut    TYPE REF TO ZBP_SALES_ORDER.
    DATA mo_double TYPE REF TO if_abap_behv_test_environment.

    CLASS-METHODS class_setup.
    METHODS setup.
    METHODS teardown.

    METHODS test_create_sets_created_by FOR TESTING.
ENDCLASS.

Initializing the Environment

CLASS ltc_sales_order IMPLEMENTATION.

  METHOD class_setup.
    " One-time: register the CDS entity and its associations
    mo_double = cl_abap_behv_test_environment=>create(
      i_root_name = 'ZSALES_ORDER_HDR' ).
  ENDMETHOD.

  METHOD setup.
    " Clear the fake buffer before each test
    mo_double->clear_doubles( ).
  ENDMETHOD.

  METHOD teardown.
    " Roll back any side effects (none with mock, but good practice)
    ROLLBACK WORK.
  ENDMETHOD.

Writing a Test

  METHOD test_create_sets_created_by.
    " Arrange — prepare input
    DATA(ls_input) = VALUE zbp_sales_order_hdr=>tt_create(
      ( %cid = 'order_1'
        SoldToParty = 'CUST-0042'
        Currency    = 'EUR' ) ).

    " Act — call the handler directly
    mo_cut = NEW #( ).
    mo_cut->create(
      entities = ls_input
      mapped   = DATA(ls_mapped)
      failed   = DATA(ls_failed)
      reported = DATA(ls_reported) ).

    " Assert
    cl_abap_unit_assert=>assert_initial(
      act = ls_failed-salesorder
      msg = 'Create should not fail' ).

    READ ENTITIES OF ZSALES_ORDER_HDR IN LOCAL MODE
      ENTITY SalesOrder
        FIELDS ( CreatedBy )
        WITH VALUE #( ( %key = ls_mapped-salesorder[ 1 ]-%key ) )
      RESULT DATA(lt_read)
      FAILED DATA(ls_read_failed).

    cl_abap_unit_assert=>assert_equals(
      exp = sy-uname
      act = lt_read[ 1 ]-CreatedBy
      msg = 'CreatedBy must be set to current user' ).
  ENDMETHOD.

ENDCLASS.

What the Mock Framework Does Under the Hood

When you call MODIFY ENTITIES ... IN LOCAL MODE inside augmentCreate, the mock framework intercepts it and:

  1. Writes the entity data to an in-memory buffer (keyed by %key)
  2. Returns a synthetic %cid_ref mapping in mapped-
  3. Lets subsequent READ ENTITIES IN LOCAL MODE pull from that same buffer

No database access occurs. The test runs in milliseconds.

Common Pitfalls

  • Forget IN LOCAL MODE → the real persistence layer is called, defeating the mock
  • Mixing %cid and %key incorrectly in the input → failed is populated but you don’t assert on it
  • Not calling clear_doubles in setup → state leaks between test methods