This is a test message to test the length of the message box.
Login
ABAP RAP ABAP Unit
Created by Software-Heroes

RAP - ABAP Unit (Business Object)

610

At this point, we'll take a look at how we can test our RAP Business Object automatically in order to check all important functions at the touch of a button.



In an older series, we already looked at ABAP Unit and how you can use it to test your code automatically. In this article we look at how you can get your RAP object validated and what you can check in the most useful way.

 

ABAP Unit

This term includes all methods for automatically testing ABAP code in the system, and there are various techniques for making the code testable. On our overview page you will find everything about ABAP Unit and how you can use it, in this article we assume the knowledge as a basis.

 

Write test

We now create a global test class for the test cases. While we could create a local test class, we only want to test the public interfaces of our RAP object and separate the test from the original object. To do this, we create the following test class as a wrapper for the tests:

"! @testing ZBS_I_RAPPartner
CLASS zcl_bs_demo_unit_rap DEFINITION PUBLIC FINAL CREATE PUBLIC
  FOR TESTING
  DURATION SHORT
  RISK LEVEL HARMLESS.

  PUBLIC SECTION.
  PROTECTED SECTION.
  PRIVATE SECTION.
    METHODS:
      create_new_entry FOR TESTING,
      fill_empty_streets FOR TESTING,
      clear_empty_streets FOR TESTING.
ENDCLASS.


CLASS zcl_bs_demo_unit_rap IMPLEMENTATION.
  METHOD create_new_entry.

  ENDMETHOD.


  METHOD fill_empty_streets.

  ENDMETHOD.


  METHOD clear_empty_streets.

  ENDMETHOD.
ENDCLASS.

 

We create the test class as a global class. The corresponding additions (FOR TESTING, DURATION, RISK LEVEL) must also be specified. We link the test class to the Core Data Service via the ABAP Doc comment. To do this, we want to implement three tests:

  • Creating a new entry and checking the generated key
  • Filling an entry with an empty street
  • Delete all entries with an empty street

 

New entry

In the first step, the test fills a table with new data and marks the errors as relevant, then we insert the new data records and commit the data to the database. Then we check the return for errors. Finally, we read from the database whether the new entry was actually created.

DATA:
  lt_new_partner TYPE TABLE FOR CREATE ZBS_I_RAPPartner.

lt_new_partner = VALUE #(
  ( partnername = 'Do it Yourself'
    street = 'Waterloo Street 13'
    city = 'London'
    country = 'GB'
    paymentcurrency = 'GBP'
    %control-PartnerName = if_abap_behv=>mk-on
    %control-Street = if_abap_behv=>mk-on
    %control-City = if_abap_behv=>mk-on
    %control-Country = if_abap_behv=>mk-on
    %control-PaymentCurrency = if_abap_behv=>mk-on
  )
).

MODIFY ENTITIES OF ZBS_I_RAPPartner
  ENTITY Partner CREATE FROM lt_new_partner
  MAPPED DATA(ls_mapped).

COMMIT ENTITIES
  RESPONSE OF ZBS_I_RAPPartner
    REPORTED DATA(ls_commit_reported)
    FAILED DATA(ls_commit_failed).

cl_abap_unit_assert=>assert_initial( ls_commit_reported-partner ).
cl_abap_unit_assert=>assert_initial( ls_commit_failed-partner ).

SELECT SINGLE FROM zbs_dmo_partner
  FIELDS partner, name
  WHERE name = 'Do it Yourself'
  INTO @DATA(ls_partner_found).

cl_abap_unit_assert=>assert_subrc( ).

 

Fill empty street

In the first step we transfer a partner for whom we want to carry out the action, we confirm the action with a commit and then read the current status from the database. The street should now read "EMPTY" and the entry should be found by the database.

DATA:
  lt_fill_streets TYPE TABLE FOR ACTION IMPORT ZBS_I_RAPPartnerPartner~fillEmptyStreets.

lt_fill_streets = VALUE #(
  ( PartnerNumber = '2000000001' )
).

MODIFY ENTITIES OF ZBS_I_RAPPartner
  ENTITY Partner EXECUTE fillEmptyStreets FROM lt_fill_streets
  MAPPED DATA(ls_mapped)
  FAILED DATA(ls_failed)
  REPORTED DATA(ls_reported).

COMMIT ENTITIES
  RESPONSE OF ZBS_I_RAPPartner
    REPORTED DATA(ls_commit_reported)
    FAILED DATA(ls_commit_failed).

SELECT SINGLE FROM zbs_dmo_partner
  FIELDS partner, Street
  WHERE partner = '2000000001'
  INTO @DATA(ls_partner_found).

cl_abap_unit_assert=>assert_subrc( ).
cl_abap_unit_assert=>assert_equals( act = ls_partner_found-street exp = 'EMPTY' ).

 

Delete EMPTY entries

For the execution of the static action we have to fill the table with an empty entry so that the logic is executed at all. Then the action is executed and confirmed with a commit. Finally, we read the database again to see if there are any records with EMPTY.

DATA:
  lt_clear_streets TYPE TABLE FOR ACTION IMPORT ZBS_I_RAPPartnerPartner~clearAllEmptyStreets.

INSERT INITIAL LINE INTO TABLE lt_clear_streets.

MODIFY ENTITIES OF ZBS_I_RAPPartner
  ENTITY Partner EXECUTE clearAllEmptyStreets FROM lt_clear_streets
  MAPPED DATA(ls_mapped)
  FAILED DATA(ls_failed)
  REPORTED DATA(ls_reported).

COMMIT ENTITIES
  RESPONSE OF ZBS_I_RAPPartner
    REPORTED DATA(ls_commit_reported)
    FAILED DATA(ls_commit_failed).

SELECT FROM zbs_dmo_partner
  FIELDS partner
  WHERE street = 'EMPTY'
  INTO TABLE @DATA(lt_empty_streets).

cl_abap_unit_assert=>assert_subrc( exp = 4 ).

 

After running the unit test we now get a result, but will this test always be consistent and can we always expect the same data for the test? In the next section we will tackle this problem.

 

Mock data

The first test cases have now been created, but run against the real data in the database. If this data changes or records are deleted, our test cases will no longer work. We now want to remove these dependencies by replacing the data in the database with new test data at the time of the test, so that we always have the right test data for our test cases. To do this, we work with a test double for the underlying Core Data Service, which means we need a CDS double.

In the first step we expand the PRIVATE SECTION and create a variable to hold the double. Furthermore, we implement the methods CLASS_SETUP and CLASS_TEARDOWN to prepare the data for the test:

PRIVATE SECTION.
  CLASS-DATA:
    go_environment TYPE REF TO if_cds_test_environment.

  CLASS-METHODS:
    class_setup RAISING cx_static_check,
    class_teardown.

 

Then we create the double for the interface view on the reference of the table and activate the redirection to the test double in the framework. The teardown method is only implemented in order to break down the test double again at the end.

METHOD class_setup.
  DATA:
    lt_partner TYPE STANDARD TABLE OF zbs_dmo_partner WITH EMPTY KEY.

  go_environment = cl_cds_test_environment=>create(
      i_for_entity = 'ZBS_I_RAPPARTNER'
      i_dependency_list = VALUE #( ( name = 'ZBS_DMO_PARTNER' type ='TABLE' ) )
  ).

  lt_partner = VALUE #(
    ( partner = '2000000001' name = 'Las Vegas Corp' country = 'US' payment_currency = 'USD' )
    ( partner = '2000000002' name = 'Gorillas' street = 'Main street 10' country = 'DE' payment_currency = 'EUR' )
    ( partner = '2000000003' name = 'Tomato Inc' street = 'EMPTY' country = 'AU' payment_currency = 'AUD' )
  ).

  go_environment->insert_test_data( lt_partner ).
  go_environment->enable_double_redirection( ).
ENDMETHOD.


METHOD class_teardown.
  go_environment->destroy( ).
ENDMETHOD.

 

For our test, we implement a different "number range" to separate the data from the original data. Furthermore, we only add some test data to keep the test clear.

 

Complete example

Here is the entire test class again with all test methods, as always you can also find them in the series' Git repository:

"! @testing ZBS_I_RAPPartner
CLASS zcl_bs_demo_unit_rap DEFINITION PUBLIC FINAL CREATE PUBLIC
  FOR TESTING
  DURATION SHORT
  RISK LEVEL HARMLESS.

  PUBLIC SECTION.
  PROTECTED SECTION.
  PRIVATE SECTION.
    CLASS-DATA:
      go_environment TYPE REF TO if_cds_test_environment.

    CLASS-METHODS:
      class_setup RAISING cx_static_check,
      class_teardown.

    METHODS:
      create_new_entry FOR TESTING,
      fill_empty_streets FOR TESTING,
      clear_empty_streets FOR TESTING.
ENDCLASS.


CLASS zcl_bs_demo_unit_rap IMPLEMENTATION.
  METHOD class_setup.
    DATA:
      lt_partner TYPE STANDARD TABLE OF zbs_dmo_partner WITH EMPTY KEY.

    go_environment = cl_cds_test_environment=>create(
        i_for_entity = 'ZBS_I_RAPPARTNER'
        i_dependency_list = VALUE #( ( name = 'ZBS_DMO_PARTNER' type ='TABLE' ) )
    ).

    lt_partner = VALUE #(
      ( partner = '2000000001' name = 'Las Vegas Corp' country = 'US' payment_currency = 'USD' )
      ( partner = '2000000002' name = 'Gorillas' street = 'Main street 10' country = 'DE' payment_currency = 'EUR' )
      ( partner = '2000000003' name = 'Tomato Inc' street = 'EMPTY' country = 'AU' payment_currency = 'AUD' )
    ).

    go_environment->insert_test_data( lt_partner ).
    go_environment->enable_double_redirection( ).
  ENDMETHOD.


  METHOD class_teardown.
    go_environment->destroy( ).
  ENDMETHOD.


  METHOD create_new_entry.
    DATA:
      lt_new_partner TYPE TABLE FOR CREATE ZBS_I_RAPPartner.

    lt_new_partner = VALUE #(
      ( partnername = 'Do it Yourself'
        street = 'Waterloo Street 13'
        city = 'London'
        country = 'GB'
        paymentcurrency = 'GBP'
        %control-PartnerName = if_abap_behv=>mk-on
        %control-Street = if_abap_behv=>mk-on
        %control-City = if_abap_behv=>mk-on
        %control-Country = if_abap_behv=>mk-on
        %control-PaymentCurrency = if_abap_behv=>mk-on
      )
    ).

    MODIFY ENTITIES OF ZBS_I_RAPPartner
      ENTITY Partner CREATE FROM lt_new_partner
      MAPPED DATA(ls_mapped).

    COMMIT ENTITIES
      RESPONSE OF ZBS_I_RAPPartner
        REPORTED DATA(ls_commit_reported)
        FAILED DATA(ls_commit_failed).

    cl_abap_unit_assert=>assert_initial( ls_commit_reported-partner ).
    cl_abap_unit_assert=>assert_initial( ls_commit_failed-partner ).

    SELECT SINGLE FROM zbs_dmo_partner
      FIELDS partner, name
      WHERE name = 'Do it Yourself'
      INTO @DATA(ls_partner_found).

    cl_abap_unit_assert=>assert_subrc( ).
  ENDMETHOD.


  METHOD fill_empty_streets.
    DATA:
      lt_fill_streets TYPE TABLE FOR ACTION IMPORT ZBS_I_RAPPartnerPartner~fillEmptyStreets.

    lt_fill_streets = VALUE #(
      ( PartnerNumber = '2000000001' )
    ).

    MODIFY ENTITIES OF ZBS_I_RAPPartner
      ENTITY Partner EXECUTE fillEmptyStreets FROM lt_fill_streets
      MAPPED DATA(ls_mapped)
      FAILED DATA(ls_failed)
      REPORTED DATA(ls_reported).

    COMMIT ENTITIES
      RESPONSE OF ZBS_I_RAPPartner
        REPORTED DATA(ls_commit_reported)
        FAILED DATA(ls_commit_failed).

    SELECT SINGLE FROM zbs_dmo_partner
      FIELDS partner, Street
      WHERE partner = '2000000001'
      INTO @DATA(ls_partner_found).

    cl_abap_unit_assert=>assert_subrc( ).
    cl_abap_unit_assert=>assert_equals( act = ls_partner_found-street exp = 'EMPTY' ).
  ENDMETHOD.


  METHOD clear_empty_streets.
    DATA:
      lt_clear_streets TYPE TABLE FOR ACTION IMPORT ZBS_I_RAPPartnerPartner~clearAllEmptyStreets.

    INSERT INITIAL LINE INTO TABLE lt_clear_streets.

    MODIFY ENTITIES OF ZBS_I_RAPPartner
      ENTITY Partner EXECUTE clearAllEmptyStreets FROM lt_clear_streets
      MAPPED DATA(ls_mapped)
      FAILED DATA(ls_failed)
      REPORTED DATA(ls_reported).

    COMMIT ENTITIES
      RESPONSE OF ZBS_I_RAPPartner
        REPORTED DATA(ls_commit_reported)
        FAILED DATA(ls_commit_failed).

    SELECT FROM zbs_dmo_partner
      FIELDS partner
      WHERE street = 'EMPTY'
      INTO TABLE @DATA(lt_empty_streets).

    cl_abap_unit_assert=>assert_subrc( exp = 4 ).
  ENDMETHOD.
ENDCLASS.

 

Conclusion

Implementing a unit test for a RAP business object is not difficult, but only by mocking the data from the database can stable tests be built. In the end, these tests also ensure that the object is used sensibly when it is extended.


Included topics:
RAPBTPABAP Unit
Comments (0)



And further ...

Are you satisfied with the content of the article? We post new content in the ABAP area every Friday and irregularly in all other areas. Take a look at our tools and apps, we provide them free of charge.


RAP - Popup Default values

Category - ABAP

How can you provide the user with default values in the popup of an action in RAP? In this article we will extend our application.

01/21/2025

RAP - Popup Mandatory Fields

Category - ABAP

How can you actually define required fields for a popup in RAP? In this article we will go into the details in more detail.

01/14/2025

RAP - Deep Table Action

Category - ABAP

Is it currently possible to pass tables to actions in RAP? This article is intended to provide a better insight into the topic.

01/07/2025

ABAP Cloud - Programming Model

Category - ABAP

Which programming model is used with ABAP Cloud and what can we learn from its predecessor? More details in the article.

01/03/2025

RAP - Side Effects

Category - ABAP

How can you update parts of the Fiori UI without doing a complete refresh? With Side Effects, this is easily possible in ABAP and RAP.

12/27/2024