Introduction

mockA, an open source ABAP mocking framework has been released recently. The today’s blog post gives you a brief introduction of the features of mockA.

Mocking Frameworks are usually used in unit tests. Their main task is the creation of test double instances more easily than e.g. manually creating local test classes which implement an interface from which the system under test depends.

This tutorial will show you how to create so called “fakes”. Fakes usually show some kind of behavior when called. In contrast “Mocks” usually also verify that some interaction with them took please. Mocks will be subject to a future blog post.

Where to get mockA

You can download the mocking framework from Github.

Install the daily build using the latest SAPLink release.

If you like it, feel free to participate in the development of the tool.

Mocking methods with returning parameters

Interface ZIF_MOCKA_IS_IN_TIME_INFO is the interface which is to be mocked in this tutorial. The interface ships with the release of mockA. Please note that no global class implements this interface. Nevertheless, we will create local classes at runtime which allow us to create objects which can eventually be called within the same report.

Before we can create a mock object, we need to tell mockA which interface is subject to mocking:

DATA lo_mocker TYPE REF TO zif_mocka_mocker.
 lo_mocker = zcl_mocka_mocker=>zif_mocka_mocker~mock( 'zif_mocka_is_in_time_info' ).

In the second step, we need to tell the mocker, which method will be mocked.

Please note, that you can easily use the fluent API by directly telling the mocker, which input should lead to which method’s output:

lo_mocker->method( 'GET_DELAY' )->with( i_p1 = 'LH' i_p2 = '123' i_p3 = '20131022' )->returns( 30 ).

In the end, the local class implementing ZIF_MOCKA_IS_IN_TIME_INFO can be created and the mock object will be instantiated.

DATA lo_is_in_time_info TYPE REF TO zif_mocka_is_in_time_info.lo_is_in_time_info ?= lo_mocker->generate_mockup( ).

Calling the mock object with some registered method input will return the specified output.

DATA lv_delay TYPE int4.
 "will return lv_delay = 30
 lv_delay = lo_is_in_time_info->get_delay( iv_carrid = 'LH' iv_connid = '123' iv_fldate = '20131022' ).

Please note, that registering the same method call pattern twice leads to different method output when the method is called multiple times with parameters that fit to the registered pattern:

lo_mocker = zcl_mocka_mocker=>zif_mocka_mocker~mock( 'zif_mocka_is_in_time_info' ).
 lo_mocker->method( 'GET_DELAY' )->with(
 i_p1 = 'LH' i_p2 = '123' i_p3 = '20131022'
 )->returns( 30 ).
 lo_mocker->method( 'GET_DELAY' )->with(
 i_p1 = 'LH' i_p2 = '123' i_p3 = '20131022'
 )->returns( 15 ).

 lo_is_in_time_info ?= lo_mocker->generate_mockup( ).
 "will return lv_delay = 30
 lv_delay = lo_is_in_time_info->get_delay(
 iv_carrid = 'LH' iv_connid = '123' iv_fldate = '20131022' ).
 "will return lv_delay = 15
 lv_delay = lo_is_in_time_info->get_delay(
 iv_carrid = 'LH' iv_connid = '123' iv_fldate = '20131022' ).

Mocking methods with exporting parameters

EXPORTING parameters can be returned by using the EXPORTS-Method

 lo_mocker = zcl_mocka_mocker=>zif_mocka_mocker~mock(
 'zif_mocka_is_in_time_info' ).
 lo_mocker->method( 'GET_BOTH' )->with(
 i_p1 = 'LH' i_p2 = '123' i_p3 = '20131023'
 )->exports( i_p1 = 2 i_p2 = abap_true ).
 lo_is_in_time_info ?= lo_mocker->generate_mockup( ).

 "will return lv_delay = 2, lv_is_in_time = 'X'
 lo_is_in_time_info->get_both(
 EXPORTING
 iv_carrid     = 'LH'
 iv_connid     = '123'
 iv_fldate     = '20131023'
 IMPORTING
 ev_delay      = lv_delay
 ev_is_in_time = lv_is_in_time
 ).

Mocking methods with changing parameters

CHANGING parameters can serve both as input and output:

lo_mocker = zcl_mocka_mocker=>zif_mocka_mocker~mock( 'zif_mocka_is_in_time_info' ).

 lo_mocker->method( 'IS_IN_TIME_BY_CHANGING_PARAM' )->with(
 i_p1 = 'LH' i_p2 = '123' )->with_changing( i_p1 = lv_fldate
 )->changes( '20131025' )->exports( i_p1 = abap_true ).

 lo_is_in_time_info ?= lo_mocker->generate_mockup( ).

 "will return lv_fldate = '20131025', lv_is_in_time = 'X'
 lo_is_in_time_info->is_in_time_by_changing_param(
 EXPORTING iv_carrid = 'LH' iv_connid = '123'
 IMPORTING ev_is_in_time = lv_is_in_time
 CHANGING cv_fldate = lv_fldate ).

Raise exceptions

Often unit tests also test unusual situations which are handled by raising and catching an exception. The mocker allows you to register a to-be-raised exception by using the methods RAISES( … ) or RAISES_BY_NAME( … )

 lo_mocker = zcl_mocka_mocker=>zif_mocka_mocker~mock( 'zif_mocka_is_in_time_info' ).
 lo_mocker->method( 'IS_IN_TIME' )->with(
 i_p1 = 'LH' i_p2 = '123' i_p3 = '20131024' )->raises_by_name( 'zcx_mocka_in_time_exception' ).
 lo_is_in_time_info ?= lo_mocker->generate_mockup( ).
 TRY.
 lo_is_in_time_info->is_in_time(
 iv_carrid = 'LH' iv_connid = '123' iv_fldate = '20131024' ).
 CATCH zcx_mocka_in_time_exception.
           BREAK-POINT."program flow will halt here
 CATCH cx_root.
           BREAK-POINT."will not be called
 ENDTRY.

Useful links

The Art of Unit Testing

How a Mocking Framework can be used

To report this post you need to login first.

10 Comments

You must be Logged on to comment or reply to a post.

  1. Paul Hardy

    I was debugging this last night to see how it worked. I already have some ideas for improvements which I will let the world know about in due course once I am sure they really are improvements.

    I have a current project where I have a large number of methods I need to mock, I will give this a whirl using this mocking method and no doubt this will enable me to give you some feedback.

    Cheersy Cheers

    Paul

    (0) 
  2. Mehmet Metin

    Finally a mocking framework ready to be implemented and it works like a charm! It is already saving me some coding lines! As a object oriented unit-test fan, I am very pleased.

    I noticed that ‘some’ exception handling within the framework is in German (only); something we can translate or enhance as a community.

    Also, I think it will be good (necessary) that the mocked method is not possible to execute in ‘production code’, now it is. I mean that it should only be possible to mock something when ‘unit testing’ (statement ‘FOR TESTING’). Of course, even the most inexperienced ABAP-er would not add a mocked method within ‘normal code’, but he/she is not being prevented to do so.

    (0) 
    1. Uwe Kunath Post author

      Dear Mehmet,

      thank you very much for testing the framework and giving feedback.

      Did you try the latest commit and still experience some german exceptions? If so, please provide some details at https://github.com/uweku/mockA/issues/1

      I also included your feedback in https://github.com/uweku/mockA/issues/2

      Would it be possible to provide some sample code to determine the switch here? Alternatively (even better), feel free to pull the code and enhance it.

      Regards,

      Uwe

      (0) 
    2. Uwe Kunath Post author

      Dear Mehmet,

      The check is carried out with the current release. It checks the settings which can be found in transaction SAUNIT_CLIENT_SETUP .

      regards,

      Uwe

      (0) 
  3. Ethan Jewett

    Great library. Really really helpful. I do have a hint for others and a question 🙂

    First, the hint:

    When mocking an interface that aliases methods from another interface (if_http_reponse aliases methods from if_http_entity), it seems you need to mock the full method from the other interface, not the aliased method. If you mock the interface method, when your code calls the alias it will get the value you mock. But if you mock the alias you get nothing, or so it seems to me.

    So, if you want to mock if_http_response->get_cdata( ), you need to lo_mocker->method(‘if_http_entity~get_cdata’)->return(‘Foo’).

    And the questions:

    Are there plans to mock properties directly? I know, it’s a no-no to have public properties, but many of SAP’s built-in classes use them – for example if_http_client->response.

    And, on the topic of mocking if_http_client, it seems it’s actually not possible as I get an error that using old exception types isn’t allowed. I guess that class must use them. Is that expected behavior?

    I’m trying to figure out getting ZAKE set up so I can contribute back. Will start sending you pull requests when/if I manage it 🙂

    Cheers,

    Ethan

    (0) 
    1. Uwe Kunath Post author

      Dear Ethan,

      thank you for the feedback. Feel free to enhance it. If you have any questions, let me know.
      The description of the behaviour is correct. Unit Test ZTEST_CL_MOCKA_MOCKER -> mock_included_interface_method
      The caller needs to specify the full length method name to define its output. Defining aliases is not yet supported. There could be a fix in the future so I wrote an issue https://github.com/uweku/mockA/issues/1

      Mocking attributes is not difficult, however not yet supported. I wrote https://github.com/uweku/mockA/issues/2

      Old style exceptions are not yet supported, however, the generation of the mock object should not fail
      https://github.com/uweku/mockA/issues/3

      I will take a further look once I find some time.

      Thank you,
      Regards,

      Uwe

      (0) 
    2. Uwe Kunath Post author

      Dear Ethan,

      please try it again using the newest build and see if you can mock if_http_client now. At least it should create a mock object without old style exceptions now.

      regards,

      Uwe

      (0) 
      1. Ethan Jewett

        Hi Uwe,

        Great, thanks so much! I will try it out today. I wasn’t sure if you were using Github issues for new feature ideas, but I’ll bring these sorts of questions there in the future. Will also put some more work into trying to get ZAKE rolling so I can contribute back.

        Cheers,

        Ethan

        (0) 
        1. Uwe Kunath Post author

          Hi,

          Despite the fact that you can access instance attributes of interfaces directly without the use of a mocking framework, I built in support for attributes.

          This might be handy when mocking specific non-final classes whose instance attributes are to be set by the mocking framework.

          As mockA mocks, when mocking specific classes, only redefines the methods which are explicitely specified in the mocker using the ->method( … )  call and leaves the other implementations intact, some of these methods may rely on mocked attributes.

          Please see the current release. Unit Test report ZTEST_CL_MOCKA_MOCKER, Methods “mock_attribute”, “mock_attribute_of_class” and “mock_invalid_attribute” document how to use (or not use) the feature.

          Regards,

          Uwe

          (0) 

Leave a Reply