Unit Test with Test Injection

light111Mock Objects and Unit Test with Injection


My last post on unit test  was how to implement abap unit test with : setup and teardown framework . ABAP Unit Test with Setup & Teardown Methods 

This post is about how to implement TEST injection to mock DB access objects and achieve unit test.


Consider you have one method of a class on which you have to perform unit test. Why unit test because with unit test driven development, the code would be stable. Even if later you need to change the code depending on further customer requirement, with unit test you will get to know that with additional code, you have not break the existing working software unit. Even if you break the pre-existing code functionality, you will get to know with unit test while changing the code lines for the new requirement.

Consider you have a class having methods under test. The methods on which that you are going to perform or write unit test, having access to the DB (customizing DB reads as example) and performs some additional checks on the DB records.  Unit test mostly performed on the development environment and here we need to mock the data base access. The class methods under test must use a helper class to gain the DB access and such a helper class that provides all the DB access works with single responsibility principle.


Let’s work on a simple FLIGHT model method and perform the unit test. This method just tells whether on particular date for a specific airline, seat is available or not.

The class- ZCL_GET_FLIGHT_DETAILS designed based on single responsibily principle and provides DB access. It has one method that gives DB record details.

Perfect the Helper class is ready now.

12


Next here is a class ZCL_FLIGHT_AVAILABILITY and having one method CHECK_AVAILABILITY under unit test.  This method CHECK_AVAILABILITY needs to access the above helper class to get the DB details and then performs additional check to decide whether seat is available or not.

3

The class under test, creates a reference to the helper DB access class. 

4

The class under test, must have to implement CONSTRUCTOR method to  support the TEST injection.  Its having a optional reference parameter which points to the helper DB class.

If the REFERENCE is supplied ( during unit test scenario) just assign it else create the object of the Helper DB class(during normal production scenario).

5

The method under test just class the Helper DB access class and then performs some checks on this to say seat is available or not.

6

The helper class must say who are its friend classes, to gain access of the protected or private methods( if any). In this case the helper class having a protected method and we have to call this helper class protected method, from the main class under test. 

13.jpg


When you call the method ZCL_FLIGHT_AVAILABILITY->CHECK_AVAILABILITY from any report ( in production i.e not unit test), then while you have to create the object of the class ZCL_FLIGHT_AVAILABILITY then you should not pass the optional parameter to the constructor. So that it works with the helper class ZCL_GET_FLIGHT_DETAILS and DB access is performed.


Now our design is ready to work with mock object with constructor injection.

Lets create a Local Test Class of the global class ZCL_FLIGHT_AVAILABILITY . Select the Local Test Classes button.

7


So here we have to perform unit test on the method CHECK_AVAILABILITY in different possible scenarios.

Very first we have to create a subclass of the Helper Class ZCL_GET_FLIGHT_DETAILS

and redefine the method to send some hard coded value instead of DB access.

89


Now create a local unit test class . Create two test methods as we want to test two test scenarios for the method  ZCL_FLIGHT_AVAILABILITY->CHECK_AVAILABILITY with two different data sets.

Declare a reference of the SUBClass of the Helper DB access class. In unit test mode, we want to run the SUBClass redefined method instead of the actual helper class method.

14.jpg

Implement the first test method. First create the object of the Helper Subclass and call the method: SET_GLOBALS to set some global variables ( random numbers in our cases). This is an additional method that we need to help us to assign some random values during unit testing.

Then create the object of the class under test ZCL_FLIGHT_AVAILABILITY and while calling the constructor pass the reference of the helper subclass. This is called as CONSTRUCTOR INJECTION. 

Now then call the method under test ZCL_FLIGHT_AVAILABILITY->CHECK_AVAILABILITY . When this method will internal calls the method, it points to the local helper subclass not to the main helper class and makes MOCK of actual DB read.

  METHOD check_availability.
    CALL METHOD gr_db_flight->get_details
      EXPORTING
        iv_carrid iv_carrid
        iv_connid iv_connid
        iv_fldate iv_fldate
      IMPORTING
        es_flight DATA(ls_flight).

    DATA(lv_availablels_flightseatsmax – ls_flightseatsocc.
    IF lv_available > 0.
      rv_true abap_true.
    ELSE.
      rv_true abap_false.
    ENDIF.

  ENDMETHOD.

11

Second test method, to check with different data set.

12


Local Test Class – Code details


* Create a SUB class of the helper class and redefine the method
* In UNIT test, the DB access to be MOCKED
CLASS lcl_get_flight_details DEFINITION INHERITING FROM zcl_get_flight_details.
PUBLIC SECTION.
METHODSset_globals IMPORTING iv_seatsmax TYPE sflight-seatsmax
                               iv_seatsocc TYPE sflight-seatsocc.
PROTECTED SECTION.
METHODSget_details REDEFINITION.

PRIVATE SECTION.
DATAgv_seatsmax TYPE sflight-seatsmax.
DATAgv_seatsocc TYPE sflight-seatsocc.
ENDCLASS.

CLASS lcl_get_flight_details IMPLEMENTATION.
METHODset_globals.
gv_seatsmax iv_seatsmax.
gv_seatsocc iv_seatsocc.
ENDMETHOD.

METHOD get_details.
* DB access Mocked here
es_flight-seatsmax gv_seatsmax.
es_flight-seatsocc gv_seatsocc.
ENDMETHOD.

ENDCLASS.

CLASS lcl_flight_availability DEFINITION FOR TESTING
DURATION SHORT RISK LEVEL HARMLESS.
PUBLIC SECTION.
METHODStest_check_availability_true  FOR TESTING.
METHODStest_check_availability_false FOR TESTING.
PRIVATE SECTION.
DATAlr_flight_check TYPE REF TO zcl_flight_availability.
DATAlr_db_flight    TYPE REF TO lcl_get_flight_details.
ENDCLASS.

CLASS lcl_flight_availability IMPLEMENTATION.

METHOD test_check_availability_true.
CREATE OBJECT lr_db_flight.
CALL METHOD lr_db_flight->set_globals
EXPORTING
iv_seatsmax 500
iv_seatsocc 400.

CREATE OBJECT lr_flight_check
EXPORTING
ir_db_flight lr_db_flight.

CALL METHOD lr_flight_check->check_availability
EXPORTING
iv_carrid 'AA'
iv_connid '0017'
iv_fldate '20141126'
RECEIVING
rv_true   DATA(lv_true).

cl_abap_unit_assert=>assert_equals(
EXPORTING
act lv_true
exp abap_true
msg 'Seats Available' ).
ENDMETHOD.

METHOD test_check_availability_false.
CREATE OBJECT lr_db_flight.
CALL METHOD lr_db_flight->set_globals
EXPORTING
iv_seatsmax 500
iv_seatsocc 500.

CREATE OBJECT lr_flight_check
EXPORTING
ir_db_flight lr_db_flight.

CALL METHOD lr_flight_check->check_availability
EXPORTING
iv_carrid 'AA'   " Airline Code
iv_connid '0017'
iv_fldate '20141126'
RECEIVING
rv_true   DATA(lv_true).

cl_abap_unit_assert=>assert_equals(
EXPORTING
act lv_true
exp abap_false
msg 'Seats Not Available' ).
ENDMETHOD.
ENDCLASS.

 


Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s