RAP - ABAP Unit (Service)
How do you actually test the service of an interface or RAP app automatically? In this article we look at unit tests for OData services.
Table of contents
In the last article we looked at ABAP Unit for the RAP Business Object and how you can use it to test the various functions automatically. Today we look at automating the tests for the OData service and how you can best carry out the implementation.
Write test
In the first step, we start again with the test class wrapper, using a global class for the test, which we connect to the service we are testing via ABAP Doc. The shell of the class looks like this:
"! @testing ZBS_UI_SIMPLE_PARTNER_O2
CLASS zcl_bs_demo_unit_service DEFINITION PUBLIC FINAL CREATE PUBLIC
FOR TESTING
DURATION SHORT
RISK LEVEL HARMLESS.
PUBLIC SECTION.
PROTECTED SECTION.
PRIVATE SECTION.
TYPES:
BEGIN OF ts_key,
PartnerNumber TYPE ZBS_C_RAPPartner-PartnerNumber,
END OF ts_key.
METHODS:
get_all_partners FOR TESTING RAISING cx_static_check,
update_unison FOR TESTING RAISING cx_static_check,
delete_nevada_inc FOR TESTING RAISING cx_static_check.
ENDCLASS.
CLASS zcl_bs_demo_unit_service IMPLEMENTATION.
METHOD get_all_partners.
ENDMETHOD.
METHOD update_unison.
ENDMETHOD.
METHOD delete_nevada_inc.
ENDMETHOD.
ENDCLASS.
We define three tests to validate the functionality of the service:
- Read all records
- Adaptation of a data set
- Delete a record
However, before we start testing, we need a test client for each test. Here you have the option to create one for OData V2 or V4. Since we want to test our V2 service, the creation looks like this, passing the service and the version:
ro_result = cl_web_odata_client_factory=>create_v2_local_proxy(
VALUE #( service_id = 'ZBS_UI_SIMPLE_PARTNER_O2' service_version = '0001' )
).
Hint: You can use the class to create different types of clients, the local proxy is required for the tests.
Read
After we have created a test client, we create a read request on the partner entity, execute it without a filter and get the result back. We check whether the table is filled and whether there is a certain entry:
DATA:
lt_result TYPE TABLE OF ZBS_C_RAPPartner.
DATA(lo_cut) = create_test_client( ).
DATA(lo_request) = lo_cut->create_resource_for_entity_set( 'Partner' )->create_request_for_read( ).
DATA(lo_response) = lo_request->execute( ).
lo_response->get_business_data( IMPORTING et_business_data = lt_result ).
cl_abap_unit_assert=>assert_not_initial( lt_result ).
cl_abap_unit_assert=>assert_not_initial( lt_result[ PartnerName = 'Unison' ] ).
Update
In order to update a data record, we create an update request via the client. To do this, we have to enter the key after the entity and select the PUT operation. We then transfer the new data and execute the request. At the end we check against the database whether the record has actually been changed.
DATA(lo_cut) = create_test_client( ).
DATA(lo_request) = lo_cut->create_resource_for_entity_set( 'Partner'
)->navigate_with_key( VALUE ts_key( PartnerNumber = '3000000003' )
)->create_request_for_update( /iwbep/if_cp_request_update=>gcs_update_semantic-put ).
lo_request->set_business_data( VALUE ZBS_C_RAPPartner( Street = 'Updated' ) ).
lo_request->execute( ).
SELECT SINGLE FROM zbs_dmo_partner
FIELDS *
WHERE partner = '3000000003'
INTO @DATA(ls_result).
cl_abap_unit_assert=>assert_equals( act = ls_result-street exp = 'Updated' ).
Delete
Deleting works similarly to updating, except that at the end we create a delete request and don't pass any data. At the end we check if the record has been deleted from the database.
DATA(lo_cut) = create_test_client( ).
DATA(lo_request) = lo_cut->create_resource_for_entity_set( 'Partner'
)->navigate_with_key( VALUE ts_key( PartnerNumber = '3000000001' )
)->create_request_for_delete( ).
lo_request->execute( ).
SELECT SINGLE FROM zbs_dmo_partner
FIELDS *
WHERE partner = '3000000001'
INTO @DATA(ls_partner).
cl_abap_unit_assert=>assert_subrc( exp = 4 ).
Hint: Depending on which features you have implemented, update or delete will not work in the test. Without an implemented feature control, the tests work as usual.
Mock data
We are currently using the real data from the database for the tests, but this does not speak for repeatable tests. Therefore we should mock the data so that we use the same data for each test and thus also ensure that the data is always available and in the state that we need. To do this, we define the following variables and methods in the 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 can implement CLASS_SETUP, in this case we create a CDS double for the consumption view, since the service works on this view. The table is also supplied with its data as a dependent object. Finally, we activate the double so that the data is drawn correctly:
DATA:
lt_partner TYPE STANDARD TABLE OF zbs_dmo_partner WITH EMPTY KEY.
go_environment = cl_cds_test_environment=>create(
i_for_entity = 'ZBS_C_RAPPARTNER'
i_dependency_list = VALUE #( ( name = 'ZBS_DMO_PARTNER' type ='TABLE' ) )
).
lt_partner = VALUE #(
( partner = '3000000001' name = 'Nevada Inc' country = 'US' payment_currency = 'USD' )
( partner = '3000000002' name = 'REWE' street = 'Main street 10' country = 'DE' payment_currency = 'EUR' )
( partner = '3000000003' name = 'Unison' street = 'Side road 16' country = 'AU' payment_currency = 'AUD' )
).
go_environment->insert_test_data( lt_partner ).
go_environment->enable_double_redirection( ).
In the CLASS_TEARDOWN we then take care that the CDS double is also removed cleanly:
go_environment->destroy( ).
The data is now stored cleanly for each test case and you don't have to worry about what the data looks like in the database. At the end you always get the current test data for your test cases.
Complete example
The full example is back here at the end of the article and includes the complete test class:
"! @testing ZBS_UI_SIMPLE_PARTNER_O2
CLASS zcl_bs_demo_unit_service DEFINITION PUBLIC FINAL CREATE PUBLIC
FOR TESTING
DURATION SHORT
RISK LEVEL HARMLESS.
PUBLIC SECTION.
PROTECTED SECTION.
PRIVATE SECTION.
TYPES:
BEGIN OF ts_key,
PartnerNumber TYPE ZBS_C_RAPPartner-PartnerNumber,
END OF ts_key.
CLASS-DATA:
go_environment TYPE REF TO if_cds_test_environment.
CLASS-METHODS:
class_setup RAISING cx_static_check,
class_teardown.
METHODS:
get_all_partners FOR TESTING RAISING cx_static_check,
update_unison FOR TESTING RAISING cx_static_check,
delete_nevada_inc FOR TESTING RAISING cx_static_check,
create_test_client
RETURNING VALUE(ro_result) TYPE REF TO /iwbep/if_cp_client_proxy
RAISING
cx_static_check.
ENDCLASS.
CLASS zcl_bs_demo_unit_service 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_C_RAPPARTNER'
i_dependency_list = VALUE #( ( name = 'ZBS_DMO_PARTNER' type ='TABLE' ) )
).
lt_partner = VALUE #(
( partner = '3000000001' name = 'Nevada Inc' country = 'US' payment_currency = 'USD' )
( partner = '3000000002' name = 'REWE' street = 'Main street 10' country = 'DE' payment_currency = 'EUR' )
( partner = '3000000003' name = 'Unison' street = 'Side road 16' 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_test_client.
ro_result = cl_web_odata_client_factory=>create_v2_local_proxy(
VALUE #( service_id = 'ZBS_UI_SIMPLE_PARTNER_O2' service_version = '0001' )
).
ENDMETHOD.
METHOD get_all_partners.
DATA:
lt_result TYPE TABLE OF ZBS_C_RAPPartner.
DATA(lo_cut) = create_test_client( ).
DATA(lo_request) = lo_cut->create_resource_for_entity_set( 'Partner' )->create_request_for_read( ).
DATA(lo_response) = lo_request->execute( ).
lo_response->get_business_data( IMPORTING et_business_data = lt_result ).
cl_abap_unit_assert=>assert_not_initial( lt_result ).
cl_abap_unit_assert=>assert_not_initial( lt_result[ PartnerName = 'Unison' ] ).
ENDMETHOD.
METHOD update_unison.
DATA(lo_cut) = create_test_client( ).
DATA(lo_request) = lo_cut->create_resource_for_entity_set( 'Partner'
)->navigate_with_key( VALUE ts_key( PartnerNumber = '3000000003' )
)->create_request_for_update( /iwbep/if_cp_request_update=>gcs_update_semantic-put ).
lo_request->set_business_data( VALUE ZBS_C_RAPPartner( Street = 'Updated' ) ).
lo_request->execute( ).
SELECT SINGLE FROM zbs_dmo_partner
FIELDS *
WHERE partner = '3000000003'
INTO @DATA(ls_result).
cl_abap_unit_assert=>assert_equals( act = ls_result-street exp = 'Updated' ).
ENDMETHOD.
METHOD delete_nevada_inc.
DATA(lo_cut) = create_test_client( ).
DATA(lo_request) = lo_cut->create_resource_for_entity_set( 'Partner'
)->navigate_with_key( VALUE ts_key( PartnerNumber = '3000000001' )
)->create_request_for_delete( ).
lo_request->execute( ).
SELECT SINGLE FROM zbs_dmo_partner
FIELDS *
WHERE partner = '3000000001'
INTO @DATA(ls_partner).
cl_abap_unit_assert=>assert_subrc( exp = 4 ).
ENDMETHOD.
ENDCLASS.
Conclusion
In addition to testing the RAP object, you can now also test the service and populate it with test data without working on the real data. In the RAP environment, there are these two types of tests (business object and service).