Skip to Content
Personal Insights
Author's profile photo Joachim Rees

Claiming ABAPUnit – Again. Part 3: I slowly get why interfaces are good and how mocking works.

If you follow my activities, you know that I am getting into ABAP Unit, and sometimes I share my learnings and questions.

Those are some recent posts:
https://blogs.sap.com/2021/12/05/claiming-abapunit-again.-a-walktrough-part-1/
https://blogs.sap.com/2021/12/06/claiming-abapunit-again.-part-2-more-tests-some-thoughts-and-adt-is-awsome/

So, I kind of know for a long time that one should “program against an interface, not a class”, but I never really did.
But recently I started to extract interfaces and see how working with them is.

Now I can share a benefit I experienced:
Of course my parameter class has no public methods to set or clear a parameter. And also, it should not have that (parameters a read from customizing tables).
But my mock parameter class I define in the unit test, can very well have such methods (and I can create another one, whenever needed).
And as the both implement the interface, I can use any of them.

[This is probably a textbook example, so it might be not much news to you – but for me, experiencing this in my own, real work is so much more impressive than reading or hearing about the theoretical concept. I think it really clicked]

In an effort of “don’t repeat yourself” I tried putting what I want to tell about my test in comments. So the core of this blog post is this code block:

*There is a check, that the stock passed in has to have certain characteristics,
*e.g. a stock type (cat), that is defined as "available".
*The definition which stock types are "available" (e.g. F1, F2) is done in customizing-tables
*and read by and stored in the parameter object (zifewm_mon_stock_mover_params ), that is passed into
*the stock_mover (code under test = cut).
  METHOD is_not_available_stock_cat.
"[given:]
    mt_mock_data = VALUE #( ( cat = 'B6' ) ).          "Data, simulating a line in /SCWM/MON: it has a certain value for stock type. (all other fields don't matter)
    mref_mock_prameters->clear_stock_cat_available( ). "Make sure that stock type is not in the list of acceptable stock types.

    "create the stock mover, passing in the mock data and the mock parameters object.
    cut = NEW zclewm_mon_stock_mover( it_data = mt_mock_data
                                      iref_parameters = mref_mock_prameters ).

"[when:]
*we can now call the public method that contains the check.
    TRY.
        cut->check_and_move_stock( ).
      CATCH zclewm_cx_input_false .
"[then:]
        "if this check fails, an exception is thrown. This is the expected result, so the test passes: we are done here:
        RETURN. "ok!
    ENDTRY.

    "only if for some reason there was NO exception, we reach this point. This is NOT what we expected. The test failed, so this is what we do:
    cl_abap_unit_assert=>fail( ).
  ENDMETHOD.

Questions / thoughts:

– how does this kind of presentation – code block with comments – work for you?
– how would I test the other way round? -> let the check pass? (Do I even need to test that ?)
– my public method is check_and_move_stock -> do some checks, and if all pass, create a warehouse task. Should that be 2 public methods instead ? ( I have more thoughts on that , but this is probably for another post)

best
Joachim

Assigned Tags

      3 Comments
      You must be Logged on to comment or reply to a post.
      Author's profile photo Andrea Borgia
      Andrea Borgia

      Yup, learning by doing! I finally "got" interfaces when I wrote unit tests on a system too old to support ATDF and interfaces allowed me to mock DB access.

      Author's profile photo Thales Batista
      Thales Batista

      Learning by doing is the best way!

      About test the other way around (and also about assert=>fail), your validation throws an object exception, it would be a cleaner UT code if you did:

      ...
            CATCH zclewm_cx_input_false into data(input_failed).
          ENDTRY.
      cl_abap_unit_assert=>assert_bound(input_failed).

      because this cuts down a few explanations to understand what is the expected behavior on this test, since the code is now talking "it is expected that exception cx_input_false" was thrown (object is bound), otherwise something is wrong. And when is expected to pass you just use assert_not_bound.

      You should write a test to it? Maybe, it really depends on your needs, your knowledge about your own (company) code and complexity of that code, and your own point of view of what need to be tested through UT. Using your own code block (and the feeling I got from the comments) 'B6' test should be enough to validate the underlying rule; writing a second test just for a different value (or loop through values) would be waste of time. If you unit tested all possible exceptional cases, then the successful move is only the realization of all prior tests, so no need to code "expected successful move" test.

      Using a standard but a non-released function module (or other code interface)? Maybe just one test for successful creation, to ensure that your call still works whenever a SP update happens. Or just not, leave that check through manual/automated SP update testing/validation.

      Trivial tests should be avoided, but sometimes ABAP trivial languages features are the own root cause of problems (like implicit conversion, non-fixed arithmetic, ' ' not being the same as ` `, still the one to be blamed is the developer, it must know those things), so you add a test to if after the code under test failed on production by that cause.

      In conclusion, "what to test and to not test" in unit tests is really decided by own experience.

      Author's profile photo Joachim Rees
      Joachim Rees
      Blog Post Author

      Thanks a lot for your all your feedback!

      The cl_abap_unit_assert=>assert_bound(input_failed). is something I will use right away!