Tuesday, 18 April 2017

Simulate Mockito in ABAP

What is Mockito?

Mockito is a mocking framework, JAVA-based library that is used for effective unit testing of JAVA applications. In our unit test there are usually some dependency on other external service implementation for example network or database service. Usually in order to isolate our test code from these dependencies we have to create mock class against them. Mockito in Java can create transient mock class ( which is only available in current test session ) for us in a very easy way. See one example below:

This is a very simple class for book manager. Three books are hard coded. If requested book index is bigger than 3, then message 1 “not exist” returns.
In this blog, I will first introduce how Mockito is used in Java to mock this manager class, then show you how the logic of Mockito could be implemented in ABAP.

class BookManager{
private String[] books = {"Inside Java", "Inside ABAP", "Inside JavaScript"};
public String getBook(int index){
if( index < books.length)
return books[index];
return "not exist";
}
}

Use Mockito in Java


The magic is located in line 18 ~ 23.
First in line 18 a proxy class is generated based on BookManager which is to be mocked, then line 20~23 we told the Mockito “when the method getBook with specified argument ( 0, 1, 2, 3 ) are called, please return given mocked data “mocked book {index}” accordingly.

SAP ABAP Tutorials and Materials, SAP ABAP Guide, SAP ABAP Certifications

And no doubt we can get the following output in console:

mocked book 0
mocked book 1
mocked book 2
mocked book 3

How does it work in Java


Now let me unveil the mistery for you. In order to make the logic better understood, I split the one line

"when(mockedManager.getBook(0)).thenReturn("mocked book 0");"

into the following equivalence:

SAP ABAP Tutorials and Materials, SAP ABAP Guide, SAP ABAP Certifications

The mock process actually consists of four steps:

Step1: create a proxy class by inheriting BookManager.class dynamically.
The generated class is a sub class of BookManager created via CGLIB, which I have already demonstrate how to implement it in ABAP in this blog:

Implement CGLIB in ABAP

SAP ABAP Tutorials and Materials, SAP ABAP Guide, SAP ABAP Certifications

Step2: mockedManager.getBook(0):

Since we call getBook on the mocked class, the original implementation of this method can never be called again. When you click F5 to debug into this method,
you can find the method call of getBook is intercepted into Mockito framework code:

SAP ABAP Tutorials and Materials, SAP ABAP Guide, SAP ABAP Certifications

Inside handler.handle(invocation) in line 47, first an instance of OngoingStubbingImpl is created for later usage.
In line 93, Mockito will check whether there is available mocked data ( In Mockito the terminology for “mocked data” is “answer” ) for current metod call in method findAnswerFor. If available, go to IF branch in line 97 to return mocked data, or else return default answer, in my example I don’t prepare any mocked data for getBook, so it simply returns null.

SAP ABAP Tutorials and Materials, SAP ABAP Guide, SAP ABAP Certifications

The logic of this step could be described in the following activity diagram:

SAP ABAP Tutorials and Materials, SAP ABAP Guide, SAP ABAP Certifications

Step3: Object stubbing = when(“dummy, any string here is ok”);
OngoingStubbingImpl<String> stub = (OngoingStubbingImpl<String>)stubbing;
The static method call simply returns the OngoingStubbingImpl instance created in step2. We need call corresponding method of this instance to feed mocked data.

Step4: stub.thenReturn(“Mocked book!!!”);
This line feeds the mocked data “Mocked book!!!” for method getBook.
The answer is inserted to a ConcurrentLinkedQueue.

SAP ABAP Tutorials and Materials, SAP ABAP Guide, SAP ABAP Certifications

Now the mocked data is ready for consuming in line

System.out.println(mockedManager.getBook(0));

Since all the previous four steps for mock are done, we expect this time, the findAnswerfor will really return something.

SAP ABAP Tutorials and Materials, SAP ABAP Guide, SAP ABAP Certifications

Inside this method, each element in the queue is looped to evaluate whether it matches the mocked method call:

SAP ABAP Tutorials and Materials, SAP ABAP Guide, SAP ABAP Certifications

The evaluation is done based on three conditions: mocked class instance must be equal, method name and argument must be equal.

SAP ABAP Tutorials and Materials, SAP ABAP Guide, SAP ABAP Certifications

finally mocked data is returned for getBook in this IF branch.

SAP ABAP Tutorials and Materials, SAP ABAP Guide, SAP ABAP Certifications

How to simulate Mockito in ABAP


First this is the book manager class written in ABAP:
class ZCL_BOOK_MANAGER definition
  public
  create public .
public section.
  methods CONSTRUCTOR .
  methods GET_BOOK
    importing
      !IV_INDEX type I
    returning
      value(RV_BOOK_NAME) type STRING .
protected section.
private section.

  data MT_BOOKS type STRING_TABLE .
ENDCLASS.
CLASS ZCL_BOOK_MANAGER IMPLEMENTATION.
  METHOD constructor.
    mt_books = VALUE string_table( ( CONV #('Inside Java') )
     ( CONV #( 'Inside ABAP' ) )
     ( CONV #( 'Inside Javascript' ) ) ).
  ENDMETHOD.
  method GET_BOOK.
    rv_book_name = value #( mt_books[ iv_index ] DEFAULT 'not exist' ).
  endmethod.
ENDCLASS.

And this is the report to test:

REPORT zmockito1.

DATA(lo_stub) = zcl_abap_mockito=>mock( 'ZCL_BOOK_MANAGER' ).
CHECK lo_stub IS NOT INITIAL.
DATA(lo_mock) = CAST zcl_book_manager( lo_stub ).

zcl_abap_mockito=>when( lo_mock->get_book( 1 ) )->then_return( 'Data 1' ).
zcl_abap_mockito=>when( lo_mock->get_book( 2 ) )->then_return( 'Data 2' ).
zcl_abap_mockito=>when( lo_mock->get_book( 3 ) )->then_return( 'Data 3' ).
zcl_abap_mockito=>when( lo_mock->get_book( 4 ) )->then_return( 'Data 4' ).

WRITE: / lo_mock->get_book( 1 ).
WRITE: / lo_mock->get_book( 2 ).
WRITE: / lo_mock->get_book( 3 ).
WRITE: / lo_mock->get_book( 4 ).

Execution result:

SAP ABAP Tutorials and Materials, SAP ABAP Guide, SAP ABAP Certifications

Still I will explain how the mock is done in ABAP via the same four steps as in Java Mockito.

Step1: Create a transient sub class dynamically via ABAP call:

zcl_abap_mockito=>mock

The implementation has already been explained in this blog: Implement CGLIB in ABAP

Step2:

lo_mock->get_book( 1 )

When the get_book is executed, of course the original implementation will never be called again. Instead, the mocked class will delegate this call to Mockito framework method 

zcl_abap_mockito=>get_mocked_data

SAP ABAP Tutorials and Materials, SAP ABAP Guide, SAP ABAP Certifications

In zcl_abap_mockito=>get_mocked_data, just the same as Java, there is a IF branch:

SAP ABAP Tutorials and Materials, SAP ABAP Guide, SAP ABAP Certifications

Step3: call method when to return the instance which is the counterpart for “OngoingStubbingImpl” in Java.

Step4: call method then_return( ‘Data 1’ ) to feed mocked data for get_book method.
The source code of zcl_abap_mockito could be found from my github.

No comments:

Post a Comment