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