Show TOC

Private Dependency Injection for ABAP Unit TestingLocate this document in the navigation structure

Procedure

At design time or when you refactor code, you can enable better unit testing by allowing dependencies to be injected into your code. Your ABAP Unit tests can then substitute test doubles for the production entities wherever you have enabled dependency injection.

This section shows how you might set up private injection of dependencies in ABAP. This is a useful pattern if you do not want to expose critical dependencies to users of a class, as you might do with constructor dependency.

Private Dependency Injection

If you declare a local test class to be a LOCAL FRIEND of the class under test, then the test class can access protected and private components of the class. This is useful if the test class should test private and protected methods. But it also allows the test class to inject dependencies that are declared as private attributes of the class under test.

Private injection by a local friend allows you to break dependencies but still protect them from manipulation from outside the class under test.

Here is some sample code showing how to set up private injection:

*----------------------------------*
* In CLASS_UNDER_TEST...
*----------------------------------*

*----------------------------------*
* The dependency needed by 
* CLASS_UNDER_TEST is instantiated
* in the CONSTRUCTOR method. The  
* constructor sets the private variable 
* MY_DEPENDENCY to reference the 
* depended-on object.
*----------------------------------*
CLASS class_under_test DEFINITION CREATE PUBLIC.
  PUBLIC SECTION.
    METHODS constructor.
    METHODS method_under_test
      RETURNING
        value(useful_value) TYPE i.
  PRIVATE SECTION.
    DATA my_dependency TYPE REF TO if_my_dependency.
ENDCLASS.

CLASS class_under_test IMPLEMENTATION.
  METHOD constructor.
    CREATE OBJECT my_dependency TYPE REF TO cl_my_dependency.
  ENDMETHOD.          

  METHOD  method_under_test.
      useful_value = my_dependency->calc_value( ).
  ENDMETHOD.
ENDCLASS. 



*----------------------------------*
* In the local ABAP Unit include
* Goto -> Local Definitions/
*   Implementations -> Local Test 
*   Classes... 
*----------------------------------*

*----------------------------------*
* There is a local test double class
* that reimplements IF_MY_DEPENDENCY
* for use in testing.
*----------------------------------*
  CLASS ltd_test_double DEFINITION.
    PUBLIC SECTION.
      INTERFACES if_my_dependency.
      ...
  ENDCLASS.
  CLASS ltd_test_double IMPLEMENTATION.
    METHOD if_my_dependency~calc_value.
      r_value = 1.
    ENDMETHOD.
  ENDCLASS.

*----------------------------------*
* LTC_ABAP_UNIT_TESTS is declared as
* a LOCAL FRIEND of the class under 
* test. As a LOCAL FRIEND, the test
* class can access protected and 
* private members of CLASS_UNDER_TEST.
*----------------------------------*
CLASS ltc_abap_unit_tests DEFINITION DEFERRED.
CLASS class_under_test DEFINITION 
  LOCAL FRIENDS ltc_abap_unit_tests.


*----------------------------------*
* In LTC_ABAP_UNIT_TESTS, the test 
* method substitutes a test double for
* the production object referenced 
* by the private attribute MY_DEPENDENCY
*----------------------------------*
CLASS ltc_abap_unit_tests DEFINITION FOR TESTING
  DURATION SHORT
  RISK LEVEL HARMLESS
  PRIVATE SECTION.
    DATA:
      cut TYPE REF TO class_under_test.  
    METHODS: calc_value_test FOR TESTING.
ENDCLASS.       

CLASS ltc_abap_unit_tests IMPLEMENTATION.
  METHOD calc_value_test.
    DATA: useful_value TYPE i,
          test_double TYPE REF TO ltd_test_double.

    CREATE OBJECT test_double.
    CREATE OBJECT cut.

    cut->my_dependency = test_double.
    useful_value = cut->method_under_test(  ).

    cl_abap_unit_assert=>assert_equals(
      act   = useful_value
      exp   = 2 ).
  ENDMETHOD.    

            

Here is how the sample code works:

  • Instead of creating depended-on objects (dependencies) inline in its code, CLASS_UNDER_TEST in the sample does the following:

    • It declares dependencies as PROTECTED or PRIVATE attributes.

    • It instantiates these depended-on objects outside the methods that actually use them. In this case, the CONSTRUCTOR does the instantiation.

    The instantiation of depended-on objects is hidden from users of CLASS_UNDER_TEST.

  • A local helper class - LTD_TEST_DOUBLE in the sample code - provides a test version of the IF_MY_DEPENDENCY object used by CLASS_UNDER_TEST.

  • In the ABAP Unit test class - LTC_ABAP_UNIT_TESTS in the sample code - the test method creates an instance of the test double. It also creates an instance of the CLASS_UNDER_TEST, and substitutes the test double for the production MY_DEPENDENCY object for use in testing.

    An alternative to cluttering up the test method with the instantiation of the test double and CUT test object would be to do these tasks in the ABAP Unit SETUP and TEARDOWN methods.