Welcome to my blog, I am glad that you are curious about ABAP unit testing. Let’s go.
As an SAP developer coming from the Java world, I always focus on Object Oriented ABAP and believe in the high value of unit testing. So the question came once, how should I test ODATA service?
In the past I tried to build mocking classes, so that I could call the ZCL*DPC_EXT methods, but it was too difficult and had little value when compared to development efforts. Then I left the topic and decided to keep example requests in SAP Gateway Client through transaction /IWFND/GW_CLIENT. That was ok for documenting purposes, but it was not a unit test unfortunatelly.
Custom utility method for ODATA request
For me it was not ok to deploy ODATA service without unit testing. I was creating tests for business logic, but why not to verify ODATA requests within seconds? I did research and found this blog which inspired me a lot, thanks to the author:
It is a clear recipe for how to test ODATA service and it works. But it was not suitable for me as I wanted to test different filters, expand, select options easily.
So again I did homework, debugged how SAP is running Gateway Client and built a simple utility method. It uses standard SAP methods. Few lines of code allows testing ODATA requests by exact string, like the client would call it. I think this is a simple but powerful way of testing requests, as we want to keep the syntax stable, especially when API was shared with client applications:
- It gives control and verifies that for a given request we get expected results and the API is stable.
- It quickly discovers if someone has changed parameter / entity name, so that it could have side effects for existing client applications. And if that is an expected change then unit tests must be also adjusted.
- It is a good documentation of how you can call the service and what are most common requests/use cases.
I am sharing the code which does the magic. I hope that at least one of you will take a chance to create automated unit tests for ODATA service by importing this code into your system.
CLASS-METHODS call_odata_request_json_format IMPORTING add_server_url type abap_bool default abap_true odata_url TYPE string EXPORTING VALUE(json_data) TYPE any response_full_string TYPE string. METHOD call_odata_request_json_format. CLEAR: json_data, response_full_string. DATA(final_odata_url) = odata_url. IF NOT ( final_odata_url CS '$format=json' ). IF ( final_odata_url CS '?' ). final_odata_url = final_odata_url && '&$format=json'. ELSE. final_odata_url = final_odata_url && '?$format=json'. ENDIF. ENDIF. DATA(request_header) = VALUE /iwfnd/sutil_property_t( ( VALUE #( name = '~request_method' value = 'GET' ) ) ( VALUE #( name = '~request_uri' value = final_odata_url ) ) ). DATA(http_caller) = /iwfnd/cl_sutil_client_proxy=>get_instance( ). http_caller->web_request( EXPORTING it_request_header = request_header " HTTP Request Header - Table IMPORTING ev_status_code = DATA(resp_status_code) " HTTP Status Code ev_status_text = DATA(resp_status_text) " HTTP Status Text et_response_header = DATA(resp_header) " HTTP Response Header - Table ev_response_body = DATA(resp_body) " HTTP Response Body ). response_full_string = /iwfnd/cl_sutil_xml_helper=>transform_to_string( resp_body ). /ui2/cl_json=>deserialize( EXPORTING jsonx = resp_body " JSON XString CHANGING data = json_data " Data to serialize ). ENDMETHOD.
Does it look complex? Maybe. But later you will see how easy it is to call it in a test code. Let’s explain sections quickly:
- Initially we verify if the query contains $format=json and if not we add it. We want to force json format so that result can be easily formatted to structure.
- request_header uses a specific format to pass the URL into web_request later. The URL can be relative, starting with /sap/opu/odata.
- web_request method is the core method that makes the ODATA request. More parameters can be added, but in my example the basic ones are imported.
- transform_to_string method converts bytes from response into readable text, containting the response content.
- /ui2/cl_json=>deserialize is used to parse the response JSON string into the structure. If the structure has matching fields to JSON structure, data is automatically populated. Note that parameter type is any so it is up to the calling program to pass the relevant type.
Creating unit test for ODATA request
Now it is time to see how the utility method can be used for test. I put it into ZCL_UTILS_UI5 class as a static method. Let’s assume that we have a model with a MaterialSet entity and $expand navigation to_ChangeLog, which allows us to retrieve ChangeLog data on demand.
METHOD test_odata_request. " Example of material entity type definition for json data mapping, entity found by key TYPES BEGIN OF type_json_resp_material. TYPES BEGIN OF d. INCLUDE TYPE zcl_material_mpc=>ts_zmaterial_ctype. " Now example of expand entity types to_changelog type zcl_material_mpc=>ts_zmaterial_change_record. TYPES END OF d. TYPES END OF type_json_resp_material. " Example of material list type definition for json data mapping, entities found by filter TYPES BEGIN OF type_json_resp_material_list. TYPES BEGIN OF d. TYPES results TYPE STANDARD TABLE OF zcl_material_mpc=>ts_zmaterial_ctype WITH DEFAULT KEY. TYPES END OF d. TYPES END OF type_json_resp_material_list. DATA json_response_entity TYPE type_json_resp_material. DATA json_response_list TYPE type_json_resp_material_list. DATA(odata_url) = |/sap/opu/odata/SAP/ZMATERIAL_SRV/MaterialSet('00000001')|. zcl_utils_ui5=>call_odata_request_json_format( EXPORTING odata_url = odata_url IMPORTING json_data = json_response_entity response_full_string = DATA(response_string) ). cl_abap_unit_assert=>assert_equals( act = json_response_entity-d-materialnumber exp = '00000001' msg = 'Material number not retrieved from ODATA request as expected' ). odata_url = odata_url && '?$format=json&$expand=to_ChangeLog'. zcl_utils_ui5=>call_odata_request_json_format( EXPORTING odata_url = odata_url IMPORTING json_data = json_response_entity response_full_string = DATA(response_string2) ). cl_abap_unit_assert=>assert_equals( act = json_response_entity-d-materialnumber exp = '00000001' msg = 'Adding $format=json should have same effect, as it is defaulted' ). cl_abap_unit_assert=>assert_equals( act = json_response_entity-d-to_changelog-materialnumber exp = '00000001' msg = 'Expand to_ChangeLog should also fill in subentity data' ).
Example above shows just the simple calls, but it can be easily extended with any ODATA query.
Many lines of code above are used for types definitions and we use MPC class for simplification. This is because we want to have data mapped automatically by the JSON framework into corresponding fields of structure. Then it is easy to do response data verification.
As an alternative we can also process the response_string which is a JSON formatted text response, useful for debugging where we see the conents of the ODATA response.
- Any ODATA $parameters can be easily added for test, like $filter, $expand, $skip, $filter – no limitation, simply define your query as you want it.
- Local types definitions can be set up just once on top of Unit Test class, so that it is reused across test methods.
- I think that unit tests can be created in DPC_EXT class as relevant to the ODATA service, or else a new custom business class can be used.
- Note that this is testing on a live database in development system and data can be changed in the future. Make sure that data exists in tables before you do assertion on ODATA response results!
Now I can verify my ODATA service with different variants in 2.6 seconds:
There are many explanations why not to create Unit Tests. But if you create them, all in all there will be isolated areas of tested code which at the end become bigger tested areas.
SAP ODATA automated testing was too difficult for me to practice it. Now with the simple framework I can do it with little effort. I believe that these tests will be a good documentation and I try to test at least scenarios which are used by client applications.
Finally the power of unit tests is that they verify functionality within seconds and can be scheduled automatically if needed. If we practice them, they do not add time but make development faster, there are less defects in quality.
Thank you for your time and good luck with coding!