Introduction
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.
ABAP Unit Tests for ODATA
Custom utility method for ODATA request
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.
Additional notes
◉ 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!
Summary
Now I can verify my ODATA service with different variants in 2.6 seconds:
No comments:
Post a Comment