ABAP Unit - Test OData
This article is about the testability of OData Services and how you can use it to test interfaces quickly and easily.
In this article we will look at testing an OData service in two ways. Once via the classic route with a data provider class and once via the new route of a service via RAP.
In order to be able to test a data provider class, we must first create an instance of the class and an instance of the context object in order to be able to test the method properly at all. A few additional steps are necessary for this so that we can test the provider properly. In the first step, we want to bring you closer to the test case.
In our test we would like to provide an OData service for the following table that provides the typical CRUD operations. The table is populated with all possible demo fields:
We create a service using transaction SEGW and import the table as a structure. The system will suggest the names of the fields accordingly. We call the entity “DATA” for the sake of simplicity.
The complete implementation of the DPC class ZCL_TEST_UNITTEST_DPC_EXT can be found in the corresponding examples at the end of the book. Now the following test class to implement the test case for creating and reading.
CLASS ltc_odata DEFINITION FINAL FOR TESTING DURATION SHORT RISK LEVEL HARMLESS. PRIVATE SECTION. DATA: mo_cut TYPE REF TO zcl_test_unittest_dpc_ext, mo_request_context_object TYPE REF TO /iwbep/cl_mgw_request_unittst. METHODS: read_data FOR TESTING RAISING cx_static_check, create_data FOR TESTING RAISING cx_static_check, prepare_cut. ENDCLASS. CLASS ltc_odata IMPLEMENTATION. METHOD prepare_cut. mo_cut = NEW zcl_test_unittest_dpc_ext( ). DATA(ls_request_context) = VALUE /iwbep/cl_mgw_request_unittst=>ty_s_mgw_request_context_unit( technical_request = VALUE #( source_entity_set = 'DATASet' target_entity_set = 'DATASet' source_entity_type = 'DATA' target_entity_type = 'DATA' ) ). mo_request_context_object = mo_cut->/iwbep/if_mgw_conv_srv_runtime~init_dp_for_unit_test( ls_request_context ). ENDMETHOD. METHOD read_data. DATA: lt_data TYPE STANDARD TABLE OF zbs_test_data. prepare_cut( ). DATA(lt_filter) = VALUE /iwbep/t_mgw_select_option( ( property = 'CURRENCY' select_options = VALUE #( ( sign = 'I' option = 'EQ' low = 'CHF' ) ) ) ). mo_cut->/iwbep/if_mgw_appl_srv_runtime~get_entityset( EXPORTING io_tech_request_context = mo_request_context_object it_filter_select_options = lt_filter IMPORTING er_entityset = DATA(lr_entity) es_response_context = DATA(ls_response_context) ). ASSIGN lr_entity->* TO FIELD-SYMBOL(<lt_data>). lt_data = CORRESPONDING #( <lt_data> ). cl_abap_unit_assert=>assert_equals( act = lines( lt_data ) exp = 2 ). ENDMETHOD. METHOD create_data. DATA: ls_new TYPE zbs_test_data. prepare_cut( ). DATA(ls_new_data) = VALUE zbs_test_data( description = 'A set of testdata from UNIT Test' amount = '12.50' currency = 'USD' creation_user = sy-uname creation_time = sy-uzeit creation_data = sy-datum ). DATA(lo_data_provider) = NEW /iwbep/cl_cp_v2_entry_provider( REF #( ls_new_data ) ). mo_cut->/iwbep/if_mgw_appl_srv_runtime~create_entity( EXPORTING io_data_provider = lo_data_provider io_tech_request_context = mo_request_context_object IMPORTING er_entity = DATA(lr_entity) ). ASSIGN lr_entity->* TO FIELD-SYMBOL(<ls_data>). ls_new = CORRESPONDING #( <ls_data> ). cl_abap_unit_assert=>assert_not_initial( ls_new-identifier ). ENDMETHOD. ENDCLASS.
What about the actual test case and the execution? To set up the test instance, a few steps must be carried out beforehand, as we also need a context object in order to be able to execute the methods.
- Fill the context structure with the entity to be tested and the name of the set
- Call the init method of the service runtime
- Save the context object in the test class
- Reading data
- Definition of a filter to restrict the data sets
- Call of method GET_ENTITYSET with context object and additional data (filter)
- Examination of the data records found for the test
- Creating data
- Preparation of a new data set
- Creation of a data provider object with the data as a reference
- Call of method CREATE_ENTITY with context object and data provider
- Checking the new key for the test
Hint: We only partially recommend testing OData Services using this method, as the methodology does not support the full range of functions of the services. If possible, testing with service bindings should be used.
The service binding is the latest variant of providing an OData from the implementation of a business object via RAP. Eclipse provides a wizard that implements the coding in its own test class or you write your own tests. We don't want to show you at this point how you can build your own business object using RAP, but how you can create one from the finished service, generate test class and then test it. RAP by itself, would fill half a book again.
In order to be able to create the test class automatically via the wizard, you have to open the service binding of the data model for which you want to define your test cases. The object creates the corresponding endpoint and the protocol (in this case OData v2) for a service.
With a right click on the corresponding entity you can then generate a test class via the context menu.
In the wizard you only have to enter the package and the name of the test class, the rest is automatically pre-assigned. At the end a global test class is created, while the actual test cases are again in the local test classes of this class.
As with the other OData service, we will implement the example for you for reading data records and creating a new data record. The handling of the local proxy is a lot easier than working with the DPC class. For the proxy there is a version for on-premise and one for the BTP, depending on the environment you are in.
CLASS ltc_odata_service DEFINITION FINAL FOR TESTING DURATION SHORT RISK LEVEL HARMLESS. PRIVATE SECTION. DATA: mo_client_proxy TYPE REF TO /iwbep/if_cp_client_proxy. METHODS: setup RAISING cx_static_check, create_data FOR TESTING RAISING cx_static_check, read_data FOR TESTING RAISING cx_static_check. ENDCLASS. CLASS ltc_odata_service IMPLEMENTATION. METHOD setup. " On-Premise Proxy mo_client_proxy = /iwbep/cl_cp_client_proxy_fact=>create_v2_local_proxy( VALUE #( service_id = 'ZBS_UI_DEMOSIMPLERAP_O2' service_version = '0001' ) ). " BTP Proxy * mo_client_proxy = cl_web_odata_client_factory=>create_v2_local_proxy( * VALUE #( service_id = 'ZBS_UI_DEMOSIMPLERAP_O2' service_version = '0001' ) * ). ENDMETHOD. METHOD create_data. DATA: ls_business_data TYPE ZBS_C_DemoSimpleRAP, lo_request TYPE REF TO /iwbep/if_cp_request_create, lo_response TYPE REF TO /iwbep/if_cp_response_create. ls_business_data = VALUE #( Item = 'HAMMER' Description = 'A good tool for nails' ItemNumber = 4911 ). lo_request = mo_client_proxy->create_resource_for_entity_set( 'SimpleRap' )->create_request_for_create( ). lo_request->set_business_data( ls_business_data ). lo_response = lo_request->execute( ). CLEAR ls_business_data. lo_response->get_business_data( IMPORTING es_business_data = ls_business_data ). cl_abap_unit_assert=>assert_not_initial( ls_business_data-UniqueKey ). ENDMETHOD. METHOD read_data. DATA: lt_business_data TYPE TABLE OF ZBS_C_DemoSimpleRAP, lo_request TYPE REF TO /iwbep/if_cp_request_read_list, lo_response TYPE REF TO /iwbep/if_cp_response_read_lst, lo_filter_factory TYPE REF TO /iwbep/if_cp_filter_factory, lt_r_number TYPE RANGE OF ZBS_C_DemoSimpleRAP-ItemNumber. lo_request = mo_client_proxy->create_resource_for_entity_set( 'SimpleRap' )->create_request_for_read( ). lt_r_number = VALUE #( ( sign = 'I' option = 'EQ' low = 2 ) ). lo_filter_factory = lo_request->create_filter_factory( ). DATA(lo_filter_node) = lo_filter_factory->create_by_range( iv_property_path = 'ITEMNUMBER' it_range = lt_r_number ). lo_request->set_filter( lo_filter_node ). lo_request->set_top( 50 )->set_skip( 0 ). lo_response = lo_request->execute( ). lo_response->get_business_data( IMPORTING et_business_data = lt_business_data ). cl_abap_unit_assert=>assert_equals( act = lines( lt_business_data ) exp = 2 ). ENDMETHOD. ENDCLASS.
We use the Consumption View (Core Data Service) to assign the data, as the corresponding field names for the OData Service are provided here and other field names could apply to the database.
The corresponding process in the test class is briefly explained again:
- Creation of the local proxy object for the connection to the service
- Reading data
- Generating the request object for reading
- Setting the filter and TOP/SKIP values
- Execution of the request
- Reading the data from the response object
- Creating data
- Creation of the new data record
- Generation of the request object for the system
- Transfer of the data
- Execution of the request
- Evaluation of the answer (filling of the key)
Don't want to test against the real data in the database? Then you can also use a test double for the core data service that is addressed. The SQL double for the table will not work here.
To do this, simply prepare and activate the corresponding double in the setup method. The accesses via the proxy then run against the CDS double.
OData services can be tested and checked just like other objects, which should make your development more stable in the future. The provision and further development of such services will make up a large part of the resources that are required in the backend in the future.