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:
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.
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.
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.
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
Cons
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
Cons
Note