SAP ABAP Unit Test – How to deal with classes of static methods
But this is the way I do it.
Let’s say, in the mists of time, someone created a static method in a class. Now, we all know now that is a bad idea, but back then, we were young, naive and full of dreams. I remember some of my dreams. The one with the carnivorous trombones was particularly traumatic.
The class has one method:
CLASS zcl_utility DEFINITION. PUBLIC SECTION. CLASS-METHODS: is_client_open RETURNING VALUE(r_result) TYPE abap_bool. ENDCLASS. CLASS zcl_utility IMPLEMENTATION. METHOD is_client_open. IF ... r_result = abap_true. ENDIF. ENDMETHOD. ENDCLASS.
It’s naturally called by:
METHOD wot_does_something. IF zcl_utility=>is_client_open( ). ... ENDMETHOD.
But we really want to test double this, so that we can put our code in a test harness.
First create a local interface and class.
CLASS lif_utility DEFINITION. METHODS: is_client_open RETURNING VALUE(r_result) TYPE abap_bool. ENDCLASS. CLASS lcl_utility IMPLEMENTATION. METHOD is_client_open. r_result = zcl_utility=>is_client_open( ). ENDMETHOD. ENDCLASS.
In my main class I have:
...DEFINITION. PUBLIC SECTION. CLASS-METHODS class_constructor. PRIVATE SECTION. CLASS-DATA _utility TYPE REF TO lif_utility. ...IMPLEMENTATION. METHOD class_constructor. _utility = NEW lcl_utility( ). ENDMETHOD. METHOD wot_does_something. IF _utility->is_client_open( ). ... ENDMETHOD.
Now the static method call is an instance method call. And it’s a class implementing an interface, which means now we can implement a test double.
CLASS ltd_utility DEFINITION FOR TESTING. PUBLIC SECTION. INTERFACES lif_utility. ENDCLASS. ... CLASS ltc_my_test DEFINITION FOR TESTING DURATION short ... PRIVATE SECTION. DATA: utility TYPE REF TO ltd_utility, cut TYPE REF TO zmy_main_class. METHODS: setup. ... ENDCLASS. CLASS ltc_my_test IMPLEMENTATION. METHOD setup. cut = new #( ). utility = new #( ). cut->_utility = utility. ENDMETHOD. ENDCLASS.
Now, when the test methods run, instead of the lcl_utility methods being called, the ltd_utility methods will be called.
I prefer this method of injection to constructor injection. But of course, this approach will work for that as well.
Where to define the various objects
In the example, I’ve defined _utility as private. As it stands, the test class can’t see it. You could make _utility public, but public changeable attributes are not a good idea.
Within a class, there are five sections (this is most clearly seen in Eclipse).
- Global Class – the methods, attributes etc. of your class.
- Class-relevant Local Types – definitions of local classes and interfaces (but not test classes).
- Local Types – implementation of local classes and interfaces (but not test classes).
- Test Classes – test class and test doubles definitions and implementations
- Macros – do not use
In the example I would have in the Class-Relevant Local Types
INTERFACE lif_utility. ... ENDINTERFACE. CLASS lcl_utility DEFINITION. ... ENDCLASS. CLASS ltc_my_test DEFINITION DEFERRED. CLASS zmy_main_class DEFINITION LOCAL FRIENDS ltc_my_test.
In this way, my test class is a friend of the main class and so can access it’s private parts.
Other approaches are possible, but this is what I use.
As pointed out by Scott Lawton in the comments, there is a potential problem with this approach. If the constructor of the access class does some work, then that’s going to happen.
Other approaches are lazy instantiation of the access class, or as a parameter via the constructor.
My constructors in access objects are not explicitly defined, but maybe in the future someone would add one, and then my unit tests would go horribly wrong.