Skip to Content

Blogs in the Series

 

So continuing the ABAP Unit Test momentum from previous blog, we have managed to create ABAP Unit test for Odata services. The intent of this blog is to describe how to create local unit test classes for Odata services.

Example Considered

In order to keep things simple we have created an Odata service which returns the description of the document type (AUART from TVAKT table) field based on the code passed to it. So I want to test my filter functionality where we will Document type code and it shall return description. In case no description exists then ABAP Unit test shall fail.

Gateway Service created in SEGW with TVAKT table.

We used TVAKT table and created a service manually. Implemented the Get_Entityset method as shown below. The code simply returns the document type description based on the document type code passed to it.

 

  METHOD tvaktset_get_entityset.
    DATA:lt_filters TYPE                   /iwbep/t_mgw_select_option,
         ls_filter  TYPE                   /iwbep/s_mgw_select_option,
         ls_val     TYPE /iwbep/s_cod_select_option.

    READ TABLE it_filter_select_options INTO ls_filter INDEX 1.
    IF sy-subrc EQ 0.
      READ TABLE ls_filter-select_options INTO ls_val INDEX 1.
      SELECT * FROM tvakt INTO TABLE et_entityset WHERE auart EQ ls_val-low AND spras = 'E'.
    ENDIF.
  ENDMETHOD.

ABAP Unit Test Class Definition

So we started by creating the unit test class for *DPC_EXT in the eclipse.

This class has 3 methods and 3 attributes

Attributes

  1. CUT – referring to our DPC class
  2. MS_REQUEST_CONTEXT_STRUCT requesting structure to get context object
  3. MO_REQUEST_CONTEXT_OBJECT Object which will be passed to our DPC testing method.

Variable 2 and 3 were added after we faced an exception in our general flow explained below.

Methods

  1. SETUP for our CUT object creation
  2. GET_DOC_TYPE_TEXT for testing our different set of inputs.
  3. RUN_FILTER basically contains reusable code to which test parameters are passed by GET_DOC_TYPE_TEXT method.
CLASS ltc_znabheet_dpc_ext DEFINITION FOR TESTING
RISK LEVEL HARMLESS
DURATION SHORT.
  PRIVATE SECTION.
    DATA:
      " Request Structure filled to get the corresponding request context.
      ms_request_context_struct TYPE /iwbep/cl_mgw_request_unittst=>ty_s_mgw_request_context_unit,
      " Request context which is normally passed to each method call
      mo_request_context_object TYPE REF TO /iwbep/cl_mgw_request_unittst,
      " DPC referring to our extension call
      cut                       TYPE REF TO zcl_znabheet_dpc_ext.
    METHODS setup.
    METHODS run_filter
      IMPORTING
        im_auart     TYPE string
        im_entity    TYPE string
        im_entityset TYPE string.
    METHODS get_doc_type_text FOR TESTING RAISING cx_static_check.

ENDCLASS.

ABAP Unit Test Class Implementation

In method GET_DOC_TYPE_TEXT we call our RUN_FILTER method for each pair of inputs.  In RUN_FILTER method we first tried to fill the Filter internal table and call GET_ENTITYSET method with just filter table with a hope that it will return us the searched values. Sadly it did not work, we were thrown an exception.

CLASS ltc_znabheet_dpc_ext IMPLEMENTATION.
  "Created our data provider
  METHOD setup.
    CREATE OBJECT cut.
  ENDMETHOD.
  METHOD get_doc_type_text.
    " Run valid Test
    run_filter( EXPORTING im_auart = 'AG' im_entity = 'TVAKT' im_entityset = 'TVAKTSet' ).
    " Run Invalid test
    "run_filter( EXPORTING im_auart = 'ZZ' im_entity = 'TVAKT' im_entityset = 'TVAKTSet').
  ENDMETHOD.


  METHOD run_filter.
    DATA: lt_filter_select_options TYPE /iwbep/t_mgw_select_option,
          ls_filter_select_option  TYPE /iwbep/s_mgw_select_option,
          lt_filter                TYPE /iwbep/t_cod_select_options,
          ls_filter                TYPE /iwbep/s_cod_select_option,
          lr_data                  TYPE REF TO data,
          ls_context               TYPE /iwbep/if_mgw_appl_srv_runtime=>ty_s_mgw_response_context.
    FIELD-SYMBOLS: <tab> TYPE table.

    " Fill the filter field detail
    ls_filter-sign = 'I'.
    ls_filter-option = 'EQ'.
    ls_filter-low = im_auart.
    APPEND ls_filter TO lt_filter.
    ls_filter_select_option-property = im_auart.
    ls_filter_select_option-select_options = lt_filter.
    APPEND ls_filter_select_option TO lt_filter_select_options.

    "read data
    TRY .
        cut->/iwbep/if_mgw_appl_srv_runtime~get_entityset(
          EXPORTING
            io_tech_request_context   = mo_request_context_object
            it_filter_select_options     = lt_filter_select_options    " Table of select options
          IMPORTING
            er_entityset              = lr_data
            es_response_context       = ls_context
        ).

      CATCH /iwbep/cx_mgw_busi_exception.

      CATCH /iwbep/cx_mgw_tech_exception.

    ENDTRY.
    " If no data found for document type text then it is an exception
    ASSIGN lr_data->* TO <tab>.
    CALL METHOD cl_abap_unit_assert=>assert_not_initial
      EXPORTING
        act = <tab>. " Actual Data Object
  ENDMETHOD.

ENDCLASS.

So that actually led us to something related to Request context parameter is missing. On googling found a SAP Help which actually talked unit test integration and implementing a method to get the request context

ABAP Unit Test Class – Method INIT_DP_FOR_UNIT_TEST

SAP has provided a method named INIT_DP_FOR_UNIT_TEST in all DPC_EXT classes to support unit testing. This method initializes the data provider instance or request context which we will be using to pass to our service call.

We implemented this method before the service call and Bingo everything was working fine both for positive as well as negative test cases. Revised RUN_FILTER method.

METHOD run_filter.
    DATA: lt_filter_select_options TYPE /iwbep/t_mgw_select_option,
          ls_filter_select_option  TYPE /iwbep/s_mgw_select_option,
          lt_filter                TYPE /iwbep/t_cod_select_options,
          ls_filter                TYPE /iwbep/s_cod_select_option,
          lr_data                  TYPE REF TO data,
          ls_context               TYPE /iwbep/if_mgw_appl_srv_runtime=>ty_s_mgw_response_context.
    FIELD-SYMBOLS: <tab> TYPE table.
    " Fill the data set and entity names to be unit tested
    ms_request_context_struct-technical_request-source_entity_type = im_entity.
    ms_request_context_struct-technical_request-target_entity_type = im_entity.
    ms_request_context_struct-technical_request-source_entity_set = im_entityset.
    ms_request_context_struct-technical_request-target_entity_set = im_entityset.

    " Fill the filter field detail
    ls_filter-sign = 'I'.
    ls_filter-option = 'EQ'.
    ls_filter-low = im_auart.
    APPEND ls_filter TO lt_filter.
    ls_filter_select_option-property = im_auart.
    ls_filter_select_option-select_options = lt_filter.
    APPEND ls_filter_select_option TO lt_filter_select_options.

    " Every DPC class now has INIT_DP_FOR_UNIT_TEST method which is used to provide a data
    " instance which can be used for unit testing
    mo_request_context_object =  cut->/iwbep/if_mgw_conv_srv_runtime~init_dp_for_unit_test(
     is_request_context = ms_request_context_struct
     ).
    "read data
    TRY .
        cut->/iwbep/if_mgw_appl_srv_runtime~get_entityset(
          EXPORTING
            io_tech_request_context   = mo_request_context_object
            it_filter_select_options     = lt_filter_select_options    " Table of select options
          IMPORTING
            er_entityset              = lr_data
            es_response_context       = ls_context
        ).

      CATCH /iwbep/cx_mgw_busi_exception.

      CATCH /iwbep/cx_mgw_tech_exception.

    ENDTRY.
    " If no data found for document type text then it is an exception
    ASSIGN lr_data->* TO <tab>.
    CALL METHOD cl_abap_unit_assert=>assert_not_initial
      EXPORTING
        act = <tab>. " Actual Data Object
  ENDMETHOD.

Learning

In order to implement ABAP unit test, method INIT_DP_FOR_UNIT_TEST must be called to get the request context.I hope this basic blog will help in implementing local test classes for all Odata services. Feel free to provide your feedback.

To report this post you need to login first.

13 Comments

You must be Logged on to comment or reply to a post.

  1. Sergio Fraga

    Hello Nabheet,

    Thanks for this blog and for the motivation to jump into TDD when developing OData services.

    I would just like to add to everyone interesting in this topic that SAP provides a framework to automate tests in our OData developements.

    This framework is ECATT_ODATA, and it should help us definining automated tests for the OData services. I have tryed to explore a litlle but the tool, but I confess it’s not simple to use. With the tool we get a wizard to create the tests with several options, but to my understaing the tool will generate the skeleton of the test classes where we should go there and add the testing code.

    The test clasees are global and they use the adition of FRIENDS to be able to access the *DPC_EXT and MPC_EXT clases.

    After some investigation I was not really convinved by the tool but nevertheless it’s good to know it exists.

    If anyone has some good experience with this tool please share with us.

    Again, great work Nabheet. and I prefer your solution for doing Unit Testing in OData development.

    Thank you

    Sérgio Fraga

     

    (5) 
    1. nabheet madan Post author

      Thanks for the feedback Sergio Fraga.This is what i like about blogging by sharing my thoughts about unit test script i came to know ECATT_Odata from you which am not aware.  Let me try to understand the framework and how it works.   Lets keep learning and keep sharing our experience does not matter how small they are.

      (0) 
  2. Sergio Fraga

    Hello again Nabheet Madan ,

    I am using your approach with one of my OData developments and now I can test my code without using Gateway Client transaction!

    Using Unit Tests give me the confidance that the code is working as expected and it’s much more fun to work like this.

    Thanks again for your investigation.

    I would just like to refer that you have a bug in your code.

    When you fill the PROPERTY of your LT_FILTER_SELECT_OPTIONS in your test code, your are passing the value of the import parameter IM_AUART.

    The test passes because in your productive code your are always reading the first line of the filter, and in your example it’s OK because you only have 1 filter, but if you had 2+ filters you should look at the filter PROPERTY, which in your case should be ‘AUART’, since it’s the value you have defined as the ABAP field in your SEGW project.

    Just a remark to make your code bullet proof 🙂

    I would also suggest to create a repo in abapGit with your example so that anyone could pull the code and test it right away. abapGit is an amazing project and we should make use of it.

    Have a great day

    Sérgio Fraga

     

     

     

     

     

    (1) 
    1. Nabheet Madan Post author

      Thanks Sergio for the feedback. Agreed was trying to demonstrate the MVP in quick time so missed it will update.

      One small request can you please all share your experience of TDD where you have used it will help us😃

      Thanks

      Nabheet

       

      (1) 
  3. Sergio Fraga

    Hello again,

    My biggest advice is to read the book from Kent Beck, the father of TDD: Test Driven Development by Example

    Regarding OData Test Units,  I am having a problem and I would like to ask you if you know how to solve it.

    In your example, in the productive code you are using the table IT_FILTER_SELECT_OPTIONS. This table comes with the properties filled with the names of the entity properties defined in the SEGW project:

    Normally, when we use filters in OData Development, we should use the following method to extract the filters used in the request. This method will return a table with the ABAP Fields in the property and not with the names:

    *    "Get filter or select option information
        data(filter_select_options) = io_tech_request_context->get_filter( )->get_filter_select_options( ).

    The problem I am having is that if I use the method above to extract the filters from the request, when I am running the Unit Test I will have a dump, because in the get_filter( ) method there is a call to the get_entity_type() method of the service model, and it seems that in Unit Test context, the service model is not instantiated.

      METHOD /iwbep/if_mgw_req_entityset~get_filter.
    
        DATA: lo_filter_org  TYPE REF TO /iwbep/cl_mgw_req_filter.
    
    * Set Key Converter for Filter
        lo_filter_org ?= mo_filter.
        TRY.
            DATA: lo_entity_type TYPE REF TO /iwbep/if_mgw_odata_fw_etype.
    
            CALL METHOD mo_model->get_entity_type "->MO_MODEL is not instantiated!!
              EXPORTING
                iv_entity_name = mr_request->*-technical_request-target_entity_type
              RECEIVING
                ro_entity_type = lo_entity_type.
    
            CALL METHOD lo_filter_org->set_entity_type
              EXPORTING
                io_entity_type = lo_entity_type.
    
          CATCH /iwbep/cx_mgw_med_exception .
            EXIT.
        ENDTRY.
        ro_filter = mo_filter.
    
      ENDMETHOD.

    If you change your productive code to call the get_filter_select_options() instead of using IT_FILTER_SELECT_OPTIONS you will get the same issue.

    Does anyone have an ideia on how to solve this?

    Currently I am using the IT_FILTER_SELECT_OPTIONS and instead of using the ABAP field names to extract the correct filters from this table I am using the Entity Property names but I think we should always work with the ABAP field names since we are working in the backend service.

    Thanks a lot for any suggestion

    Sérgio Fraga

    (0) 
    1. Former Member

      Sergio, it seems like the ms_request_context_struct which is of type

      TYPE /IWBEP/CL_MGW_REQUEST_UNITTST=>TY_S_MGW_REQUEST_CONTEXT_UNIT

      has the ability to set the parameters.

      “`

      ” Every DPC class now has INIT_DP_FOR_UNIT_TEST method which is used to provide a data ” instance which can be used for unit testing mo_request_context_object = cut->/iwbep/if_mgw_conv_srv_runtime~init_dp_for_unit_test( is_request_context = ms_request_context_struct ).

      “`

      (0) 
  4. Former Member

     

    Hi,

    does this also work for oData-Services that are generated based on corresponding CDS Views? The mid and long term way of implementing an oData service is potentially no longer manually coding via SEGW but modelling a corrsponding CDS View with the oData Annotation.

    BR,

    Holger

     

     

    (0) 
    1. Nabheet Madan Post author

      Dear Holger

      We have two options here as per my understanding as i dont see any annotation provided in CDS which actually make sense.

      1. Option one is to use ECATT_Odata as mentioned in previous comments which will generate the framework and classes based on your CDS
      2. Manual one.

      Thanks

      Nabheet

      (0) 
      1. Former Member

        Thanks for pointing to that course.  Nice to see info about all the test doubles.  It’s one way that the io_tech_request_context might be able to be mocked.

        (0) 
  5. Former Member

    Hi Nabheet,

    Thank you for this excelent post!

    Can you atach a test example for “get_entity” implementation method? I don’t know how to set the key.

    Thank you!!

    (0) 
  6. Tobias Trapp

    Hi Nabheet,

    great work 🙂 I have another question: Do you see a chance to test the MPC_EXT class as well? The use case is to to test a redefinition of the define method.

    Best Regards,
    Tobias

    (0) 

Leave a Reply