
ABAP OO - Injector
In the last article, we looked at ABAP OO and the evolution of design patterns, and how we can best use them. Therefore, in this article, we will delve deeper into the details of the injector for testability.
Table of contents
In this article, we'll look at how the injector works and how you can use it together with the Factory to enable easy testability.
Introduction
In the last blog post in this series, we took a detailed look at the Factory and the Singleton. There, we examined the different stages of evolution and how we can further develop the pattern. You should read that article as a foundation before continuing with this one. We now want to use the factory and prepare it for our unit tests without having to change the actual code in the implementation.
Challenge
Currently, we have a global class in the system that is supposed to validate a timestamp. The class creates an instance of our implementation via the factory and passes it to the method. There, we check against a specific timestamp in the future. If you look at the code, however, we would have to wait until the unit test works once.
CLASS zcl_bs_demo_oo_test DEFINITION
PUBLIC FINAL
CREATE PUBLIC.
PUBLIC SECTION.
METHODS validate_timestamp
RETURNING VALUE(result) TYPE abap_boolean.
ENDCLASS.
CLASS zcl_bs_demo_oo_test IMPLEMENTATION.
METHOD validate_timestamp.
DATA(timestamp) = zcl_bs_demo_oo_factory=>create( ).
RETURN xsdbool( timestamp->get_timestamp( ) = '20991231063000.0000000' ).
ENDMETHOD.
ENDCLASS.
Currently, we have our component with a unit test. At runtime, the unit test calls the method, which then receives an instance back via the factory. However, our unit test won't work for many years, and then only for a very small fraction of the functionality. Therefore, the component isn't very testable at the moment.
Enhancement
To this end, we extend the scenario of the factory and the existing objects by adding another dimension. In this chapter, we will look at the various components and extend them.
Basics
From the last article, we need the following objects, although for our case we will only modify the Factory:
- ZCL_BS_DEMO_OO_FACTORY
- ZCL_BS_DEMO_OO_PRIVATE
- ZIF_BS_DEMO_OO_INTERFACE
Injector
We now need to make two modifications simultaneously. To do this, we first create the new injector ZCL_BS_DEMO_OO_INJECTOR as a global class.
CLASS zcl_bs_demo_oo_injector DEFINITION
PUBLIC ABSTRACT FINAL
CREATE PUBLIC
FOR TESTING.
PUBLIC SECTION.
CLASS-METHODS inject_double
IMPORTING double TYPE REF TO zif_bs_demo_oo_interface OPTIONAL.
ENDCLASS.
CLASS zcl_bs_demo_oo_injector IMPLEMENTATION.
METHOD inject_double.
zcl_bs_demo_oo_factory=>double = double.
ENDMETHOD.
ENDCLASS.
The injector has several properties that are important for its use:
- ABSTRACT - The class should not be instantiable, but should only function via the provided methods and attributes.
- FOR TESTING - The class should only be usable in the case of a unit test. Generally, the code is not compiled in test and production systems, but this serves as an additional safety net.
- OPTIONAL - The double is static and should also be removable for further unit tests. Therefore, we set the parameter to Optional.
Factory
In the next step, we will adapt our factory. As a first step, we define the injector as a GLOBAL FRIEND so that it can also access protected methods and attributes. Secondly, we define a static attribute that contains the DOUBLE at runtime, and as a final step, we ensure that the CREATE method returns the Double when it has been set.
CLASS zcl_bs_demo_oo_factory DEFINITION
PUBLIC ABSTRACT FINAL
GLOBAL FRIENDS zcl_bs_demo_oo_injector.
PUBLIC SECTION.
CLASS-METHODS create
RETURNING VALUE(result) TYPE REF TO zif_bs_demo_oo_interface.
PRIVATE SECTION.
CLASS-DATA double TYPE REF TO zif_bs_demo_oo_interface.
ENDCLASS.
CLASS zcl_bs_demo_oo_factory IMPLEMENTATION.
METHOD create.
IF double IS INITIAL.
RETURN NEW zcl_bs_demo_oo_private( ).
ELSE.
RETURN double.
ENDIF.
ENDMETHOD.
ENDCLASS.
Finally, don't forget to activate both objects (Injector and Factory).
Test
We have now prepared everything for our test. Let's look at the current scenario and two ways we can use the Injector effectively.
Function
Our Injector is now prepared so that we can use it to inject a test double into the Factory, which we will then use in the unit test. This way, we only test the behavior of our VALIDATE_TIMESTAMP method and no longer the dependency on its implementation in the Timestamp class.
Local Class
As a first option, we define a local helper class where we implement the interface. Since we have marked the class as FOR TESTING, we can use PARTIALLY IMPLEMENTED and do not have to implement all methods from the interface.
CLASS lhc_double DEFINITION FOR TESTING.
PUBLIC SECTION.
INTERFACES zif_bs_demo_oo_interface PARTIALLY IMPLEMENTED.
ENDCLASS.
CLASS lhc_double IMPLEMENTATION.
METHOD zif_bs_demo_oo_interface~get_timestamp.
RETURN '20991231063000.0000000'.
ENDMETHOD.
ENDCLASS.
Before we execute our test code, we use the injector, create a new instance of our helper class, and pass the instance. The injector then sets the DOUBLE in the factory, which is used for the unit test.
zcl_bs_demo_oo_injector=>inject_double( NEW lhc_double( ) ).
Test Double Framework
As a second option, we can use the Test Double Framework. With this option, we don't need the local implementation with the interface, but we do have to perform a few steps to prepare the double. You can find more information about the framework in this article.
DATA(double) = CAST zif_bs_demo_oo_interface( cl_abap_testdouble=>create( 'zif_bs_demo_oo_interface' ) ).
cl_abap_testdouble=>configure_call( double )->ignore_all_parameters( )->returning( '20991231063000.0000000' ).
double->get_timestamp( ).
As a final step, we pass the created and prepared double to the injector and run our test again.
zcl_bs_demo_oo_injector=>inject_double( double ).
Result
Both unit tests are now running and we reach the timestamp sooner and more often to get our unit tests to pass.
Complete Example
You can find the complete source code of the examples in our corresponding GitHub repository. You will find most of the coding in this article, but we have only shown the most important parts, especially regarding execution and use.
Conclusion
In this article, we wanted to introduce the injector and show you how you can use it simply and effectively in future projects to build testable code and make it available to other developers.


