0

I have a scenario where my class method (A) calls another class method (B). So A is depended on B. I’d like to get rid of the dependency to be able to run unit tests. Decoupling and dependency injection are somewhat based on instantiation. But class methods (static methods) don’t need an instance by nature. I’ve already made two solutions working, but none of them seems 100% for me:

  1. We create instance of class B (production code) or instance of the test double (for unit test). We inject it as a parameter to the class method under test. The inner class method is called on the injected instance, not on the class.

I don’t like we need to make an instance of class, although we are using class method. It can take time. It needs a bit more code.

  1. We inject actual class name as a string parameter and we use dynamic CALL METHOD.

As I am not fan of interpreted languages, I consider this a mess that can bring serious runtime problems. Since we do this to implement unit tests and consequently to eliminate possible bugs; using dynamic calls seems counterproductive. Also it is painful to work with parameters.

Is there another way to solve this? Is there some important point I missed?

Bellow, there are key parts of both solutions. There are not essential to understand the problem, but they might help.

1)

INTERFACE lif_readfile.
  CLASS-METHODS gui_upload
    IMPORTING file_path TYPE string
    CHANGING data_tab  TYPE truxs_t_text_data.
ENDINTERFACE.

CLASS lcl_file_operations DEFINITION.
  PUBLIC SECTION.
    CLASS-METHODS:
      get_file_length
        IMPORTING
          !file_path         TYPE string
        CHANGING
          !filereader        TYPE REF TO lif_readfile OPTIONAL
        RETURNING
          VALUE(text_length) TYPE i.
ENDCLASS.

CLASS lcl_file_operations IMPLEMENTATION.
  METHOD get_file_length.

*create instance of default productive class
    IF filereader IS NOT BOUND.
      filereader = NEW lcl_readfile( ).
    ENDIF.

*use instance to call class method
    filereader->gui_upload(
      EXPORTING file_path = file_path
        CHANGING data_tab = lt_data
    ).

*code under test here..

  ENDMETHOD.
ENDCLASS.

2)

CLASS lcl_file_operations DEFINITION.
  PUBLIC SECTION.
    CLASS-METHODS:
      get_file_length
        IMPORTING
          !file_path         TYPE string
          !classname         TYPE string DEFAULT 'LCL_READFILE'
        RETURNING
          VALUE(text_length) TYPE i.
ENDCLASS.

CLASS lcl_file_operations IMPLEMENTATION.
  METHOD get_file_length.

*parameter definition
    ptab = VALUE #( ( name  = 'FILE_PATH'
                      kind  = cl_abap_objectdescr=>exporting
                      value = REF #( file_path ) )
                    ( name  = 'DATA_TAB'
                      kind  = cl_abap_objectdescr=>changing
                      value = REF #( lt_data ) ) ).

    DATA(meth)     = 'LIF_READFILE~GUI_UPLOAD'.

*dynamic call
    CALL METHOD (classname)=>(meth) PARAMETER-TABLE ptab.

*code under test here..

  ENDMETHOD.
ENDCLASS.
jcjr
  • 1,503
  • 24
  • 40

2 Answers2

0

Your question is language-agnostic I think. Did you check the multiple answers which have been provided in the forum, and what do you think of the different approaches proposed?

You may wrap the static call inside new interface and classes with an instance method which maps to the static method of the right class, statically specified.

The solution specific to ABAP about using the "class name" as a variable, as you have shown, I personally don't like it, but I think it's just a matter of individual preference, not really important. PS: the term "interpreted language" is for a programming language, the opposite (to be simplistic) is "compiled language"; what you are talking about is more about a dynamic link.

Sandra Rossi
  • 11,934
  • 5
  • 22
  • 48
  • I haven’t found a single post on this very topic. And I don’t think it is agnostic. A code returning correct result (as this one) is not always the correct code (concerning performance, readability, sustainability etc.). My both solutions smell. I am looking for sbd giving better solution, or an expert saying ‘there is no better solution’ according my experience. – jcjr Jul 13 '18 at 07:47
  • @jcjr search "dependency injection static method" – Sandra Rossi Jul 13 '18 at 18:50
  • I’ve tried and I haven’t found much. It has to be related to ABAP. And in ABAP, it is usually not named ‘static’ but ‘class’ method. Moreover, class methods are always used with unit test; but just not as code under test. Only article found mentioning static class under test talks about partial solution and uses macros: https://blogs.sap.com/2013/08/28/dependency-injection-for-abap/ – jcjr Jul 16 '18 at 14:13
0

I’ve found so far two solutions that appears better to me. But if you know even a better solution; I am still looking forward to try it.

Factory+injector (dependency lookup)

Improved solution 1 – handling of instances is moved to the factory. Code for the factory and injector is not provided – it is standard solution.

CLASS lcl_file_operations DEFINITION.
  PUBLIC SECTION.
    CLASS-METHODS:
      get_file_length
        IMPORTING
          !file_path         TYPE string
        RETURNING
          VALUE(text_length) TYPE i.
ENDCLASS.


CLASS lcl_file_operations IMPLEMENTATION.
  METHOD get_file_length.

    myfactory=>get_filereader( )->gui_upload(
      EXPORTING file_path = file_path
        CHANGING data_tab = lt_data
    ).

*code under test here..

  ENDMETHOD.
ENDCLASS.

Pros

  • Cleaner and shorter code under test. Instance is created and tested somewhere else.

Cons

  • It still creates instances.
  • More code in total.

Using TEST-SEAM and TEST-INJECTION

CLASS zcl_file_operations IMPLEMENTATION.
  METHOD get_file_length.

   TEST-SEAM seam_gui_upload.
     zcl_filereader=>gui_upload(
        EXPORTING file_path = file_path
        CHANGING data_tab = lt_data
     ).
   END-TEST-SEAM.

*code under test here..

  ENDMETHOD.
ENDCLASS.

FOR TESTING method

*...
TEST-INJECTION seam_gui_upload.
 ztc_testdouble=>gui_upload(
    EXPORTING file_path = file_path
    CHANGING data_tab = lt_data
 ).
END-TEST-INJECTION.
*...

Pros

  • This seems like the best solution so far.
  • Using class-methods; no instances created.
  • Shortest code in total.

Cons

  • Considered dirty technique by community (recommended only for legacy code).
  • Slight pollution of code under test.

Note

jcjr
  • 1,503
  • 24
  • 40