Skip to Content
Author's profile photo Adam Krawczyk

Unit tests for exceptions

Hi,

In this short blog I would like to explain how can we write unit test for method which throws exception. Even if it is simple case for many, I got at least one question about it and it means that hints may be useful for others.

As far as I know there is no assertion method or built in feature in ABAP unit test framework that would check if method throws exception. It would be a good candidate to extend framework by the way. We need to handle exception situation ourselves.

Normally if exception is thrown during test method execution, test is marked as failed. We need to avoid error escalation and implement test logic in the way that controls exception and verifies if it actually has occured. I propose two similar variants for that:

Variant 1:

If exception is not thrown, we call fail method which makes test not passing. If method that we test raises exception as expected, then fail line will never be reached and test will pass automatically:

        TRY.
            mo_cut->raise_exception( ).
             cl_abap_unit_assert=>fail( 'Exception did not occur as expected' )
        CATCH cx_root.
        ENDTRY.

Variant 2:

We introduce flag that monitors if exception occurs. It has abap_false value by default and it changes only if we enter into CATCH section:

     DATA l_exception_occured TYPE abap_bool VALUE abap_false.
   
        TRY.
            mo_cut->raise_exception( ).
        CATCH cx_root.
            l_exception_occured = abap_true.
        ENDTRY.
   
        cl_abap_unit_assert=>assert_true(
          act = l_exception_occured
          msg = 'Exception did not occur as expected'
        ).

First variant is shorter but second is more self explained.

If you already use ABAP in eclipse, I recommend to create new template (Window -> Preferences -> ABAP Development -> Source Code Editor -> Templates), call it assert_exception and use it while unit tests creation just by typing “assert”, CONTROL + SPACE + assert_exception + ENTER. That helps.

It is also worth to mention that there is already assertion that checks the system status code: cl_abap_unit_assert=>assert_subrc. This method is similar to assert_equals, the difference is that we can skip act parameter as it is by default sy-subrc.

Kind regards 🙂

Adam

Assigned Tags

      9 Comments
      You must be Logged on to comment or reply to a post.
      Author's profile photo Former Member
      Former Member

      Hi Adam!

      I wanted to practise some TDD katas. There is a task to write a test method which checks an exception was thrown or wasn't. I stucked.

      After running your test methods (above) gives green results or red? I got red, and I have no idea, what is wrong.

      Code under test:

      METHOD add.

          ...

          RAISE EXCEPTION TYPE cx_local_exception

               EXPORTING

                 text = exception_text.

          ...

      ENDMETHOD.

      Test method definition:

      METHODS add_negative_params_throw_excp FOR TESTING RAISING cx_local_exception.

      Test method implementation:

      METHOD add_negative_params_throw_excp.

           DATA cx_local_exception TYPE REF TO cx_local_exception.

           DATA exception_text     TYPE string.


           TRY.

               sum = string_calculator->add( '//;\n1;-2' ).

             CATCH cx_local_exception INTO cx_local_exception.

               exception_text = cx_local_exception->get_text( ).


               cl_aunit_assert=>assert_equals( act = exception_text

                                               exp = 'Negatives not allowed: -2' ).

           ENDTRY.


           cl_aunit_assert=>fail( msg = 'No exception was thrown!' ).

      ENDMETHOD.

      It's look like my test method cannot catch the thrown exception.

      Could you help me what wrong with my test code?

      Thanks a lot!

      Alex

      Author's profile photo Adam Krawczyk
      Adam Krawczyk
      Blog Post Author

      Hi Alex,

      At least one problem that I see is that you have cl_unit_assert=>fail method at the end of test method. This is statement that always makes test as failed (red status).

      You should rather move the fail method call just under line where you calculate sum:

      TRY.

           sum = string_calculator->add( '//;\n1;-2' ).

           cl_aunit_assert=>fail( msg = 'No exception was thrown!' ).

      CATCH cx_local_exception INTO cx_local_exception.

      ...

      The goal of this approach is: make test as failure, if program is processing next line after add method. We assume that this line should be ignored and program jumps to CATCH section immediately after add method.

      In addition, your definition of test method does not need to have RAISING cx_local_exception addition. You do not want to throw exception from test method, but catch and handle exception logic inside there.

      Regards,

      Adam

      Author's profile photo Alex Gönczy
      Alex Gönczy

      Hi Adam!

      Thanks for your answer!

      Oh, you are right!! The first problem was I found two implementation of exception handling in unit tests. One of them is this:

      http://help.sap.com/erp2005_ehp_06/helpdata/de/dd/587324e2424b14ab5afb3239a77a8d/content.htm,

      the second one is yours and I confused.

      So I have changed the implementation of my test method, like you suggested above.

      But I still have a problem: I get cx_local_exception and red test, I think the raised exception wasn't handled by the test method's try and catch block (because the exception doesn't reach the handling block).

      In the CUT, only need to throw an exception?

      cx_local_exception

      definition:

      CLASS cx_local_exception DEFINITION INHERITING FROM cx_static_check. 

        PUBLIC SECTION.   

          METHODS constructor IMPORTING text TYPE string.   

          METHODS get_text    FINAL REDEFINITION.   

        PRIVATE SECTION.   

          DATA text TYPE string.

      ENDCLASS.

      implementation:

      CLASS cx_local_exception IMPLEMENTATION.

         METHOD constructor.

           super->constructor( ).

           me->text = text.

         ENDMETHOD.


         METHOD get_text.

           result = me->text.

         ENDMETHOD.               

      ENDCLASS.

      Thanks again!

      Regards,

      Alex

      Author's profile photo Adam Krawczyk
      Adam Krawczyk
      Blog Post Author

      Hi,

      With unit tests results if they are failed you get as well description.

      • If the message is "No exception was thrown" it means that method string_calculator->add did not throw exception altough it should (you expected it from test scenario).
      • If the message is empty it means that your eqality assertion failed which means that even if you cought excepton in CATCH section, the message from exception (exception_text) was not equal to "'Negatives not allowed: -2'"

      I advise you to run debugger and see exactly which line is failing, check all present values and exceptions raised.

      Regards,

      Adam

      Author's profile photo Alex Gönczy
      Alex Gönczy

      Hi Adam,

      I tried to run debugger, and found that the exception doesn't reach the handling block (after raise exception in CUT, the CUT doesn't give back the control to the test method to handle exception).

      I see this during debug mode (in if_aunit_classtest~run method implementation):

      -> catch cx_sy_no_handler into l_cx_unhandled.

      The debugger jumps to this line. It's look like the ABAP engine cannot see any try-catch block, but it is exist in the test method implementation, in the test class.

      I shared an image of ABAP Unit result:

      http://postimg.org/image/4ld2edfpj/

      So, I found that the add method throws exception -> done, in debugger mode the exception_text contains 'Negatives not allowed: -2' -> done, but I still got red test.

      Thanks for your help!

      Regards,

      Alex

      Author's profile photo Adam Krawczyk
      Adam Krawczyk
      Blog Post Author

      Hi,

      You have not posted definition of ADD method. Does it contain RAISING exception information?

      Have you deleted RAISING EXCEPTION from test method definition?

      Try to replace catch CX_LOCAL_EXCEPTION with CX_ROOT exception to see if this one will be caught. CX_ROOT is the parent for all exceptions.

      For some reason your CATCH section does not recognize exception, like different are used in RAISE and CATCH section. If you attach file with implementation and test so I can run it myself, I can find what is wrong.

      Regards,

      Adam

      Author's profile photo Alex Gönczy
      Alex Gönczy

      Hi Adam,

      Firstly thanks for your time, you solved my problem, i'm really appreciate.

      You have not posted definition of ADD method. Does it contain RAISING exception information?

      I forgot the RAISING clause from my add method definition. So now I corrected it, and finally got green tests. 🙂

      CLASS cl_string_calculator DEFINITION.  

        PUBLIC SECTION.    

          METHODS add IMPORTING i_numbers    TYPE string                 RETURNING value(r_sum) TYPE i

                      RAISING cx_local_exception.   

        PROTECTED SECTION.   

        PRIVATE SECTION.

      ENDCLASS.

      Thanks again!

      Regards,

      Alex

      Author's profile photo Mehmet Metin
      Mehmet Metin

      In addition, I also use the following variant in my unit-test for testing an exception:

      ...

      CATCH cx_root INTO lx_root.

      cl_aunit_assert=>assert_bound( act  = lx_root

                                                        msg = 'Testing exception XXX'

                                                        quit  = cl_aunit_assert=>no ).

      ENDTRY.

      Of course, no matter which approach you choose, you have to think of what you are testing. Just AN(Y) exception or a SPECIFIC exception.

      Author's profile photo Eldho Alex Kanjirathingal
      Eldho Alex Kanjirathingal

      Hi Adam,

      Thanks for some suggestions of TDD for exception handling.

      I have a test coverage problem related to exception handling.

      When ever I write a TRY .. ENDTRY in my code under testing ,

      the statement ENDTRY will not get executed in unit testing and it reduce my code coverage.

      While unit testing I tried raising  exception which are caught, not caught etc. Nothing execute ENDTRY.

      Any idea how to execute this ENDTRY statement in unit testing.

       

      Regards,

      Alex