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)

393

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 - Deep Action in OData v4

Category - ABAP

In this article we will look at actions with deep structures, how we can create them and pass data to an API endpoint.

05/24/2024

BTP - Connect On-Premise (Consumption Model v2)

Category - ABAP

In this article we want to provide another update on the connection of on-premise systems and how this is done with a communication arrangement.

12/15/2023

RAP - Show app count (Tile)

Category - ABAP

This example is about displaying a counter on the tile of a Fiori Elements application and how such a thing can be implemented.

10/06/2023

RAP - Generator (Fiori)

Category - ABAP

In this article we will look at the more complex RAP generator as a Fiori Elements application and what advantages you have with it.

09/15/2023

RAP - Generator (ADT)

Category - ABAP

Today, let's take a look at the RAP Generator, which is already integrated into ABAP Development Tools, and how you can use it to easily build new RAP apps.

09/08/2023