
ABAP Unit - Decoupling
In a modern ABAP architecture, decoupling objects is an important component for ensuring testability. The first step involves identifying and then actually replacing the dependency.
Table of contents
In this article, we'll look at decoupling dependencies within our unit tests and how important it is to remove such dependencies.
Introduction
Unit tests and ABAP Unit are an important part of modern ABAP architecture, as all our coding and business logic is based on classes. You can learn more about modern architecture in the article we published last week. Simple classes can be tested relatively quickly with ABAP Unit. But what about complex frameworks that use many classes, sub-objects, and various dependencies? This is where decoupling comes into play.
Decoupling
But first, let's look at the process of building unit tests and what we want to test in a test of our class.
Unit Tests
At the heart of this is the unit test, which in most cases will be found in the test class include of the class. This unit test, or several unit tests if we have complex classes and want to work with multiple test classes, is intended to test our actual class. This class is called the Class Under Test, or CUT for short. And our unit test should primarily focus on testing this class and its methods.
In most cases, the public part of a class is tested. However, if a class is active in processing and only has one public method, the tests for this method can become quite complex if we want to test every private method as well. Here, the methods and attributes can be very branched. Therefore, it can definitely make sense to also test the private methods of the class. The main requirement for this is stable methods that won't change too often in the future, otherwise we would have to adapt our unit tests very frequently.
Dependencies
Large dependencies on other components and frameworks within the system can arise quite quickly. For example, if we read data from a Core Data Service, for configuration purposes or for data we need, a dependency can occur. Other dependencies include function blocks that we call to trigger functions outside the system or initiate processes within the system. Or another class to which we pass the data and which we include in the processing. Furthermore, there are various other objects that we can call outside our class, thus creating a dependency on our class.
Mocking
In such a case, we want to mock these objects in our tests. This means that we receive data back or pass data in a controlled manner, as we need it for our class, without testing the actual objects. As you can see below, such objects should have their own unit tests, so we create a network within the system of different unit tests that test the various components.
Test Double Frameworks
For this purpose, the system has the Test Double Framework, or TDF for short. This consists of various classes and helper objects that provide us with different techniques for mocking components of dependencies. In recent years, this framework has grown steadily, so that we now have test doubles available from a wide variety of areas and technologies that we can use. Here are two special features we want to point out that are associated with this framework:
- The first is TEST SEAM, one of the simplest ways to replace code components. However, you should keep in mind that this should only be used in emergencies or for units that are difficult to test. This is because we can often create situations where, for example, we don't perform a proper test. If we mock a database access with Test Seam, for instance, we would be able to replace the returned data, but we wouldn't be testing whether the SELECT statement worked, whether the filters were passed correctly, and whether the fields were populated properly.
- The second case we should look at is the Authority Check. It can only remove permissions; it cannot add new ones. This is mainly because we can also issue a COMMIT WORK during a unit test to persist data records in the system or read data to which we might not even have access. Therefore, for security reasons, it is only possible to remove permissions. You should take this into account in your automation and with a technical user.
Hint: Would you like to view the current ABAP unit features and find out which release they are available in? Simply refer to the ABAP Feature Matrix.
DAOs
In the past, we have often said that you should use a Data Access Object, or DAO for short, to mock dependencies. This is still the case if you are working on older systems where the Test Double Framework is not available and you want to create a decoupling. On a modern system, you should use the Test Double Framework to remove dependencies, as this also saves a lot of code and objects that wouldn't actually be necessary.
Example
Nothing without ABAP code. Therefore, let's look at a small example of how to mock a Core Data Service and create a test class for it.
Method
In our user class, we have a method that reads the full name of the current user from the system. For this, there is a Core Data Service, I_BusinessUserBasic, which we want to read in order to return the corresponding field. This gives us a relatively easy-to-test method, because we have an input parameter and an output parameter, which we can then automate through tests.
METHOD use_core_data_service.
SELECT SINGLE FROM I_BusinessUserBasic
FIELDS PersonFullName
WHERE BusinessPartner = @user
INTO @result.
IF sy-subrc <> 0.
CLEAR result.
ENDIF.
ENDMETHOD.
Phases
When using a test framework, it's important to understand the different phases of unit testing and when each method is triggered. There are setup and teardown methods, one for the class and one for each individual test method. If you're not familiar with these in detail, we recommend that you familiarize yourself with the individual phases and methods to use them efficiently and gain a better understanding.
Test Class
In our test class, we currently only have a single unit test. For this, we also include the preparations for the test double on the standard CDS view, as well as the setup method to prepare the data, and the teardowns to clean up the test double afterward. The actual test method then simply creates the CUT object for the test, calls the test method for our test user, and we then check the result against our expectations. Initially, this involves a significant amount of effort in implementing the basic framework and mocking the data. We can then easily implement further negative tests with different data and states.
CLASS ltc_test_core_data_service DEFINITION FINAL
FOR TESTING RISK LEVEL HARMLESS DURATION SHORT.
PRIVATE SECTION.
CONSTANTS user_id TYPE string VALUE `1`.
CONSTANTS expected_name TYPE string VALUE `Ada Lovelace`.
CLASS-DATA sql_environment TYPE REF TO if_osql_test_environment.
CLASS-METHODS class_setup.
CLASS-METHODS class_teardown.
METHODS setup.
METHODS teardown.
METHODS use_core_data_service FOR TESTING RAISING cx_abap_context_info_error.
ENDCLASS.
CLASS zcl_bs_demo_deco_usage DEFINITION LOCAL FRIENDS ltc_test_core_data_service.
CLASS ltc_test_core_data_service IMPLEMENTATION.
METHOD class_setup.
sql_environment = cl_osql_test_environment=>create( i_dependency_list = VALUE #( ( 'I_BUSINESSUSERBASIC' ) ) ).
ENDMETHOD.
METHOD class_teardown.
sql_environment->destroy( ).
ENDMETHOD.
METHOD setup.
DATA business_users TYPE STANDARD TABLE OF I_BusinessUserBasic WITH EMPTY KEY.
business_users = VALUE #( ( BusinessPartner = user_id
PersonFullName = expected_name ) ).
sql_environment->insert_test_data( business_users ).
ENDMETHOD.
METHOD teardown.
sql_environment->clear_doubles( ).
ENDMETHOD.
METHOD use_core_data_service.
FINAL(cut) = NEW zcl_bs_demo_deco_usage( ).
FINAL(result) = cut->use_core_data_service( user_id ).
cl_abap_unit_assert=>assert_equals( exp = expected_name
act = result ).
ENDMETHOD.
ENDCLASS.
Hint: Even when testing a CDS view, we use the SQL double because we want to mock access to the data. The CDS double wouldn't be possible here in the ABAP Cloud environment because the underlying tables and views are not shared.
Time
Now, we probably come to the biggest factor when it comes to creating these tests: time. Many developers would now point out that they don't have the time or that no budget has been allocated within the projects to create tests for the code. We'd also like to point out that AI, and specifically Agentic AI (via GitHub Copilot, for example), can now help generate such tests quickly and easily. For instance, the tests above were generated by AI, meaning we only had to write the method implementation. The agent handled the framework setup, dependency detection, and test method creation. Therefore, in today's world, it's difficult to justify not generating unit tests, especially since they form the basis for easily and safely extending a system.
Conclusion
Knowing dependencies and eliminating them using the Test Double Framework is an important part of providing a cleanly tested environment. It's also the foundation for working safely with objects and making extensions when such tests already exist. In principle, AI can now also generate clean unit tests, identify dependencies, or at least prepare the unit tests for you.



