diff --git a/14_ABAP_Unit_Tests.md b/14_ABAP_Unit_Tests.md
index bbf32d0..c640d59 100644
--- a/14_ABAP_Unit_Tests.md
+++ b/14_ABAP_Unit_Tests.md
@@ -6,10 +6,16 @@
- [Unit Tests in ABAP](#unit-tests-in-abap)
- [High-Level Steps for ABAP Unit Tests](#high-level-steps-for-abap-unit-tests)
- [Creating Test Classes](#creating-test-classes)
- - [Creating and Implementing Test Methods](#creating-and-implementing-test-methods)
+ - [Creating Test Methods](#creating-test-methods)
+ - [Implementing Test Methods](#implementing-test-methods)
+ - [Evaluating the Test Result with Methods of the CL\_ABAP\_UNIT\_ASSERT Class](#evaluating-the-test-result-with-methods-of-the-cl_abap_unit_assert-class)
- [Special Methods for Implementing the Test Fixture](#special-methods-for-implementing-the-test-fixture)
- [Handling Dependencies](#handling-dependencies)
+ - [Creating/Implementing Test Doubles](#creatingimplementing-test-doubles)
+ - [Injecting Test Doubles](#injecting-test-doubles)
- [Test Seams](#test-seams)
+ - [Creating Test Doubles Using ABAP Frameworks](#creating-test-doubles-using-abap-frameworks)
+ - [Example: Creating Test Doubles Using ABAP Frameworks](#example-creating-test-doubles-using-abap-frameworks)
- [Running and Evaluating ABAP Unit Tests](#running-and-evaluating-abap-unit-tests)
- [More Information](#more-information)
- [Executable Example](#executable-example)
@@ -38,7 +44,7 @@ This cheat sheet contains basic information about [unit testing](https://help.sa
- Examples of DOCs: In your production code, a method is called that is outside of your code. Or, for example, whenever there is an interaction with the database.
- This means that there are dependencies that need to be taken into account for a unit test. These dependencies should be isolated and replaced by a test double.
- Create/Implement test doubles
- - You can create test doubles manually. For example, you hardcode the test data to be used when the test is executed. Note: Automatic creation is not covered here.
+ - You can create test doubles manually. For example, you hardcode the test data to be used when the test is executed. You can also use ABAP frameworks that provide a standardized approach to creating test doubles.
- Ideally, the testability of your code has been prepared by providing interfaces to the DOC. Interfaces facilitate the testability because you can simply implement the interface methods.
- Inject the test doubles to ensure that the test data is used during the test run.
- Create test classes and methods
@@ -176,7 +182,7 @@ ENDCLASS.
⬆️ back to top
-## Creating and Implementing Test Methods
+## Creating Test Methods
Test methods ...
- are special [instance methods](https://help.sap.com/doc/abapdocu_cp_index_htm/CLOUD/en-US/index.htm?file=abeninstance_method_glosry.htm) of a test class in which a test is implemented.
@@ -189,8 +195,6 @@ Test methods ...
- are called in an undefined order.
- have no parameters.
-**Creating Test Methods**
-
Example:
``` abap
"Test class in a test include
@@ -220,24 +224,14 @@ CLASS ltc_test_class IMPLEMENTATION.
ENDCLASS.
```
-**Implementing Test Methods**
+⬆️ back to top
+
+## Implementing Test Methods
+
- The implementation ideally follows the *given-when-then* pattern.
- *given*: Preparing the test, e.g. creating an instance of the class under test and a local test double (to inject the test double into the class under test)
- *when*: Calling the procedure to be tested
- - *then*: Checking and evaluating the test result using the static methods of the `CL_ABAP_UNIT_ASSERT` class
-- The following points cover a selection of static methods of the class `CL_ABAP_UNIT_ASSERT` that can be used for the checks:
- - `ASSERT_EQUALS`: Checks whether two data objects are the same.
- - `ASSERT_BOUND`: Checks whether a reference variable is bound.
- - `ASSERT_NOT_BOUND`: Negation of the one above.
- - `ASSERT_INITIAL`: Checks whether a data object has its initial value.
- - `ASSERT_NOT_INITIAL`: Negation of the one above.
- - `ASSERT_SUBRC`: Checks the value of `sy-subrc`.
- - `FAIL`: Triggers an error.
-- For the class and methods, as well as the paramters, check the F2 information in the ADT. Depending on the method used, parameters can (or must) be specified. To name a few:
- - `ACT`: of type `ANY` (in most cases), non-optional importing parameter of the methods, specifies a data object that is to be verified
- - `EXP`: of type `ANY` (in most cases), a data object holding the expected value (non-optional for the `ASSERT_EQUALS` method)
- - `MSG`: of type `CSEQUENCE`, error description
- - `QUIT`: of type `INT1`, the specification affects the unit test flow control in case of an error, e.g. the constant `if_abap_unit_constant=>quit-no` can be used to determine that the unit test should not be terminated in case of an error
+ - *then*: Checking and evaluating the test result using the static methods of the `CL_ABAP_UNIT_ASSERT` class (see next section)
The following code snippet shows a class declaration and implementation part in the production code.
A test method is to be created for the method.
@@ -315,6 +309,43 @@ ENDCLASS.
⬆️ back to top
+
+### Evaluating the Test Result with Methods of the CL_ABAP_UNIT_ASSERT Class
+
+The following overview covers a selection of static methods of the `CL_ABAP_UNIT_ASSERT` class that can be used for the checks.
+
+| Method | Details |
+|---|---|
+| `ASSERT_EQUALS` | Checks whether two data objects are the same |
+| `ASSERT_BOUND` | Checks whether a reference variable is bound |
+| `ASSERT_NOT_BOUND` | Negation of the one above |
+| `ASSERT_INITIAL` | Checks whether a data object has its initial value |
+| `ASSERT_NOT_INITIAL` | Negation of the one above |
+| `ASSERT_SUBRC` | Checks the value of `sy-subrc` |
+| `ASSERT_CHAR_CP` | Character sequence matching a pattern |
+| `ASSERT_CHAR_NP` | Negation of the one above |
+| `ASSERT_DIFFERS` | Checks whether two elementary data objects are different |
+| `ASSERT_TRUE` | Checks whether boolean is true |
+| `ASSERT_FALSE` | Checks whether boolean is false |
+| `ASSERT_NUMBER_BETWEEN` | Checks whether a number is in a given range |
+| `ASSERT_RETURN_CODE` | Checks whether the return code has a specific value |
+| `ASSERT_TABLE_CONTAINS` | Checks whether data is contained as line in an internal table |
+| `ASSERT_TABLE_NOT_CONTAINS` | Negation of the one above |
+| `ASSERT_TEXT_NOT_MATCHES` | Checks whether text does not contain/contains text matching a regular expression |
+| `ASSERT_THAT` | Checks whether a constraint is met by a data object |
+| `FAIL` | Triggers an error |
+| `SKIP` | Skips a test because of missing prerequisites |
+
+For the class and methods, as well as the paramters, check the F2 information in the ADT. Depending on the method used, parameters can (or must) be specified. To name a few:
+
+| Parameters | Details |
+|---|---|
+| `ACT` | It is of type `ANY` (in most cases), non-optional importing parameter of the methods, specifies a data object that is to be verified |
+| `EXP` | It is of type `ANY` (in most cases), a data object holding the expected value (non-optional for the `ASSERT_EQUALS` method) |
+| `MSG` | It is of type `CSEQUENCE`, error description |
+| `QUIT` | It is of type `INT1`, the specification affects the unit test flow control in case of an error, e.g. the constant `if_abap_unit_constant=>quit-no` can be used to determine that the unit test should not be terminated in case of an error |
+
+
### Special Methods for Implementing the Test Fixture
- Special private methods for implementing the test [fixture](https://help.sap.com/doc/abapdocu_cp_index_htm/CLOUD/en-US/index.htm?file=abenfixture_glosry.htm), which may include test data and test objects among others, can be included in the local test class.
- They are not test methods and the `FOR TESTING` addition cannot be used.
@@ -404,7 +435,7 @@ The code snippets above covered test classes and methods in simple contexts with
It is assumed that you have identified the dependent-on components (DOC) in a method in your production code. They were isolated (which may have involved some major rebuilding if the code is not created from scratch and the requirements for unit testing were taken into account, e.g. by creating an interface), and you want to replace them with a test double (by injection) so that your code can be properly tested without dependencies.
-**Creating/Implementing test doubles**
+### Creating/Implementing Test Doubles
As recommended, you should ideally have an interface to the DOC.
@@ -419,7 +450,7 @@ There are multiple ways to implement test doubles manually:
> **💡 Note**
> Instead of manually creating test doubles, the [ABAP OO Test Double Framework](https://help.sap.com/docs/ABAP_PLATFORM_NEW/c238d694b825421f940829321ffa326a/804c251e9c19426cadd1395978d3f17b.html?locale=en-US) helps you create test doubles automatically.
-**Injecting the test doubles**
+### Injecting Test Doubles
As described [here](https://help.sap.com/docs/ABAP_PLATFORM_NEW/c238d694b825421f940829321ffa326a/04a2d0fc9cd940db8aedf3fa29e5f07e.html?locale=en-US), there are multiple techniques for injecting test doubles to ensure that the test doubles are used during the test run.
@@ -483,6 +514,1150 @@ END-TEST-INJECTION.
⬆️ back to top
+## Creating Test Doubles Using ABAP Frameworks
+
+The previous sections covered self-made test doubles. You can use ABAP frameworks to create and inject test doubles, providing a standardized method for replacing dependent-on components (DOC) during unit tests. These DOCs can include classes, database tables/CDS views, RAP business objects, and more.
+
+Several frameworks are available and demonstrated in the example classes below. Note that there are more frameworks available that are not further covered here, e.g. for managing dependencies on function modules. For more details, refer to the [documentation](https://help.sap.com/docs/abap-cloud/abap-development-tools-user-guide/managing-dependencies-with-abap-unit).
+
+
+
+| DOC | Details |
+
+
+| Classes and interfaces |
+
+
+- You can use the ABAP OO Test Double Framework.
+- As a prerequisite, you ...
+ - have an ABAP class ready to replace the DOC. Instead of using a test double with an interface, the example below involves the class under test directly using the DOC, which is a non-final ABAP class.
+ - apply an injection mechanism to replace the original class with the test double class. The following example illustrates constructor and parameter injection, but you can find more injection mechanisms in the documentation and the executable example in the ABAP cheat sheets repository.
+- Using the framework, you have the ability to configure values for return, export, and change parameters, as well as handle exceptions and events with each method call. The `CL_ABAP_TESTDOUBLE` class supports you with the test double configuration, offering a range of functionalities, including verifying interactions on the test double.
+- However, be aware that `CL_ABAP_TESTDOUBLE` does not support certain use cases, including local classes/interfaces and classes with declarations such as `FINAL`, `FOR TESTING`, `CREATE PRIVATE`, or constructors with mandatory parameters.
+
+
+ |
+
+
+| Database (e.g. database tables or CDS view entities) |
+
+
+- You can use these framworks:
+ - CDS Test Double Framework: For testing logic implemented in CDS entities; makes use of the `CL_CDS_TEST_ENVIRONMENT` class
+ - ABAP SQL Test Double Framework: For testing ABAP SQL statements that depend on data sources such as database tables or CDS view entities; makes use the `CL_OSQL_TEST_ENVIRONMENT` class
+
+ |
+
+
+
+| RAP business object |
+
+
+- Dependencies on RAP business objects can be handled by ...
+ - creating transactional buffer test doubles (`CL_BOTD_TXBUFDBL_BO_TEST_ENV` class)
+ - mocking ABAP EML APIs (`CL_BOTD_MOCKEMLAPI_BO_TEST_ENV` class)
+
+ |
+
+
+
+
+### Example: Creating Test Doubles Using ABAP Frameworks
+
+The example classes below illustrate how to create and inject test doubles using the mentioned ABAP frameworks.
+
+To run the example, follow these steps:
+- First, import the ABAP cheat sheet repository. The example relies on some of its repository objects.
+- Next, create two classes: `ZCL_DEMO_ABAP_UNIT_TDF` (the class under test) and `ZCL_DEMO_ABAP_UNIT_DATAPROV` (the DOC containing methods called by the first class). Copy and paste the provided code into these classes. Note that `ZCL_DEMO_ABAP_UNIT_TDF` includes a test class in the test include (*Test Classes* tab in ADT).
+- The example is set up to display content in the ADT console when you run the class with *F9*. This is solely to demonstrate the effects of statements and method calls. The main focus is on test methods that explore test double creation using the frameworks. To launch the tests in the class, use *Ctrl/Cmd + Shift + F10*.
+
+> **💡 Note**
+> - The example classes are intentionally simplified and nonsemantic, designed to highlight basic unit tests and explore framework classes and methods.
+> - They are not meant to serve as a model for proper unit test design. Always devise your own solutions for each unique case.
+> - Refer to the code comments, SAP Help Portal and class documentation for additional information.
+
+
+Expand the following collapsible section to view the code of an example.
+
+
+ Expand to view the example code
+
+
+
+**Class 1 `ZCL_DEMO_ABAP_UNIT_TDF` (the class under test)**
+
+1a) Insert the following code in the `Global Class` tab
+
+
+```abap
+CLASS zcl_demo_abap_unit_tdf DEFINITION
+ PUBLIC
+ FINAL
+ CREATE PUBLIC .
+
+ PUBLIC SECTION.
+
+* -------------------------- NOTE ----------------------------------
+* - This is an example class that demonstrates managing dependencies
+* (dependent-on-components, DOC) with ABAP Unit.
+* - Topics covered:
+* - ABAP OO Test Double Framework (test doubles injected using
+* constructor and parameter injection)
+* - ABAP SQL Test Double Framework
+* - ABAP CDS Test Double Framework
+* - Managing dependencies on RAP business objects (mocking ABAP EML
+* APIs, transactional buffer test doubles)
+*
+* ----------------------- RUN ABAP UNIT TEST---------------------------
+* - Open the class with the ABAP development tools for Eclipse (ADT).
+* - Choose Ctrl/Cmd + Shift + F10 to launch all tests in the class.
+* You can also right-click in the class and choose Run as -> ABAP Unit
+* Test.
+* - The results of a test run are displayed in the ABAP Unit tab in ADT
+* and can be evaluated.
+*
+* ----------------------- RUN CLASS -----------------------------
+* - Open the class with the ABAP development tools for Eclipse (ADT).
+* - Choose F9 to run the class and check the console output.
+
+ INTERFACES if_oo_adt_classrun.
+
+ "--------------- 1) ABAP OO Test Double Framework ---------------
+ "The examples in the test include use the cl_abap_testdouble class.
+
+ "----------- 1a) Demonstrating constructor injection -----------
+
+ "Specifying the instance constructor with an optional parameter
+ "for the purpose of constructor injection as injection mechanism.
+ "In this example, methods are called from another, non-final class.
+ "They represent DOCs. Test doubles are injected when running unit
+ "tests. The parameter expects an instance of this class.
+ METHODS constructor
+ IMPORTING oref_constr_inj TYPE REF TO zcl_demo_abap_unit_dataprov OPTIONAL.
+
+ "Declaring an object reference variable that will be used to call
+ "methods of an external class (the DOC)
+ DATA oref_data_provider TYPE REF TO zcl_demo_abap_unit_dataprov.
+
+ "Method that is tested and contains a DOC. In this case, it is an
+ "external method that is called in the implementation part. The DOC
+ "is replaced by a test double created using cl_abap_testdouble.
+ METHODS td_constr_inj_calc_discount
+ IMPORTING
+ value TYPE numeric
+ RETURNING
+ VALUE(result) TYPE decfloat34.
+
+ "----------- 1b) Demonstrating parameter injection -----------
+
+ "Method that is tested and contains a DOC. In this case, it is an
+ "external method that is called in the implementation part. The DOC
+ "is replaced by a test double created using cl_abap_testdouble.
+ "The example demonstrates parameter injection. Therefore, an
+ "optional importing parameter is included. When running the unit
+ "test, 'data_prov' is bound, and the test double is injected.
+ METHODS td_param_inj_calc_discount
+ IMPORTING value TYPE numeric
+ date TYPE d
+ time TYPE t
+ data_prov TYPE REF TO zcl_demo_abap_unit_dataprov OPTIONAL
+ EXPORTING message TYPE string
+ weekday TYPE string
+ RETURNING VALUE(result) TYPE decfloat34.
+
+ "----------- 2) ABAP SQL Test Double Framework -----------
+ "The examples in the test include use the cl_osql_test_environment
+ "class. Here, a database table represents the DOC.
+ "The method includes a SELECT statement.
+
+ METHODS sql_get_shortest_flight_time IMPORTING carrier TYPE zdemo_abap_flsch-carrid
+ RETURNING VALUE(shortest_flight) TYPE i.
+
+ "----------- 3) ABAP CDS Test Double Framework -----------
+ "The examples in the test include use the cl_cds_get_data_set_environment class.
+ "Here, a CDS view entity represents the DOC. The method includes a SELECT
+ "statement. Another test method is implemented in the test inlcude that
+ "demonstrates the testing of a CDS view entity (without testing a method in the
+ "class under test).
+ METHODS cds_get_data_set IMPORTING carrier TYPE zdemo_abap_cds_ve_agg_exp-carrid
+ RETURNING VALUE(agg_line) TYPE zdemo_abap_cds_ve_agg_exp.
+
+ "----------- 4) Managing dependencies on RAP business objects -----------
+ "----------- 4a) Demonstrating mocking ABAP EML APIs --------------------
+ "----------- ABAP EML read operations -----------------------------------
+ "The examples in the test include use the cl_botd_mockemlapi_bo_test_env class.
+
+ "Populating database tables for ABAP EML read requests
+ METHODS prep_dbtab_for_eml IMPORTING read_op TYPE abap_boolean DEFAULT abap_false.
+ TYPES read_tab_ro TYPE TABLE FOR READ RESULT zdemo_abap_rap_ro_m.
+ TYPES read_tab_ch TYPE TABLE FOR READ RESULT zdemo_abap_rap_ro_m\_child.
+
+ "Method that includes an ABAP EML read request on the root entity
+ METHODS eml_read_root IMPORTING key TYPE zdemo_abap_rap_ro_m-key_field
+ RETURNING VALUE(tab_ro) TYPE read_tab_ro.
+
+ "Method that includes an ABAP EML read-by-associaton request
+ METHODS eml_rba IMPORTING key TYPE zdemo_abap_rap_ro_m-key_field
+ RETURNING VALUE(tab_ch) TYPE read_tab_ch.
+
+ "----------- ABAP EML modify operation -----------
+ TYPES modify_tab_ro TYPE TABLE FOR CREATE zdemo_abap_rap_ro_m.
+ TYPES ty_mapped TYPE RESPONSE FOR MAPPED EARLY zdemo_abap_rap_ro_m.
+ TYPES ty_failed TYPE RESPONSE FOR FAILED EARLY zdemo_abap_rap_ro_m.
+ TYPES ty_reported TYPE RESPONSE FOR REPORTED EARLY zdemo_abap_rap_ro_m.
+
+ "Method that includes an ABAP EML modify request on the root entity
+ METHODS eml_modify_root IMPORTING VALUE(instances) TYPE modify_tab_ro
+ EXPORTING mapped TYPE ty_mapped
+ failed TYPE ty_failed
+ reported TYPE ty_reported.
+
+ "----------- 4b) Demonstrating transactional buffer test doubles ---------
+ "The examples in the test include use the cl_botd_txbufdbl_bo_test_env class.
+ TYPES read_tab_ro_u TYPE TABLE FOR READ RESULT zdemo_abap_rap_ro_u.
+
+ "Method that includes an ABAP EML read request on the root entity
+ METHODS eml_read_root_buffer_td IMPORTING key TYPE zdemo_abap_rap_ro_u-key_field
+ RETURNING VALUE(tab_ro_u) TYPE read_tab_ro_u.
+
+ PROTECTED SECTION.
+ PRIVATE SECTION.
+
+ENDCLASS.
+
+
+
+CLASS zcl_demo_abap_unit_tdf IMPLEMENTATION.
+
+ METHOD if_oo_adt_classrun~main.
+ "This implementation includes method calls of those methods that are unit tested
+ "in the test include.
+ "The implementation is just for demonstration purposes to explore the effect of
+ "the methods.
+
+ "Populating demo database tables (only required for running the example class
+ "with F9 and exploring the effect of various method calls)
+ zcl_demo_abap_aux=>fill_dbtabs( ).
+
+ "----- 1) Methods demonstrating the ABAP OO Test Double Framework in the test include -----
+ "----- 1a) Using constructor injection -----
+ DATA(td_constr_inj_calc_discount) = td_constr_inj_calc_discount( 100 ).
+ out->write( data = td_constr_inj_calc_discount name = `td_constr_inj_calc_discount` ).
+ out->write( |\n| ).
+
+ td_constr_inj_calc_discount = td_constr_inj_calc_discount( 500 ).
+ out->write( data = td_constr_inj_calc_discount name = `td_constr_inj_calc_discount` ).
+ out->write( |\n| ).
+
+ td_constr_inj_calc_discount = td_constr_inj_calc_discount( CONV decfloat34( '3.4' ) ).
+ out->write( data = td_constr_inj_calc_discount name = `td_constr_inj_calc_discount` ).
+ out->write( |\n| ).
+
+ "----- 1a) Using parameter injection -----
+ "Example with Sunday
+ td_param_inj_calc_discount(
+ EXPORTING
+ value = 100
+ date = '20241201'
+ time = '100000'
+ IMPORTING
+ weekday = DATA(weekday)
+ message = DATA(message)
+ RECEIVING
+ result = DATA(td_param_inj_calc_discount)
+ ).
+
+ out->write( data = td_param_inj_calc_discount name = `td_param_inj_calc_discount` ).
+ out->write( data = weekday name = `weekday` ).
+ out->write( data = message name = `message` ).
+ out->write( |\n| ).
+
+ "Example with a weekday, morning
+ td_param_inj_calc_discount(
+ EXPORTING
+ value = 100
+ date = '20241202'
+ time = '100000'
+ IMPORTING
+ weekday = weekday
+ message = message
+ RECEIVING
+ result = td_param_inj_calc_discount
+ ).
+
+ out->write( data = td_param_inj_calc_discount name = `td_param_inj_calc_discount` ).
+ out->write( data = weekday name = `weekday` ).
+ out->write( data = message name = `message` ).
+ out->write( |\n| ).
+
+ "Example with a weekday, afternoon
+ td_param_inj_calc_discount(
+ EXPORTING
+ value = 100
+ date = '20241203'
+ time = '150000'
+ IMPORTING
+ weekday = weekday
+ message = message
+ RECEIVING
+ result = td_param_inj_calc_discount
+ ).
+
+ out->write( data = td_param_inj_calc_discount name = `td_param_inj_calc_discount` ).
+ out->write( data = weekday name = `weekday` ).
+ out->write( data = message name = `message` ).
+ out->write( |\n| ).
+
+ "Example with a weekday, night
+ td_param_inj_calc_discount(
+ EXPORTING
+ value = 100
+ date = '20241204'
+ time = '230000'
+ IMPORTING
+ weekday = weekday
+ message = message
+ RECEIVING
+ result = td_param_inj_calc_discount
+ ).
+
+ out->write( data = td_param_inj_calc_discount name = `td_param_inj_calc_discount` ).
+ out->write( data = weekday name = `weekday` ).
+ out->write( data = message name = `message` ).
+ out->write( |\n| ).
+
+ "----- 2) Methods demonstrating the ABAP SQL Test Double Framework in the test include -----
+ DATA(sql_shortest_flight_time) = sql_get_shortest_flight_time( 'LH' ).
+ out->write( data = sql_shortest_flight_time name = `sql_shortest_flight_time` ).
+ out->write( |\n| ).
+
+ sql_shortest_flight_time = sql_get_shortest_flight_time( 'AA' ).
+ out->write( data = sql_shortest_flight_time name = `sql_shortest_flight_time` ).
+ out->write( |\n| ).
+
+ "----- 3) Methods demonstrating the ABAP CDS Test Double Framework in the test include -----
+ DATA(data_set_cds) = cds_get_data_set( 'LH' ).
+ out->write( data = data_set_cds name = `data_set_cds` ).
+ out->write( |\n\n| ).
+
+ data_set_cds = cds_get_data_set( 'AA' ).
+ out->write( data = data_set_cds name = `data_set_cds` ).
+ out->write( |\n\n| ).
+
+ "----- Methods demonstrating mocking ABAP EML APIs in the test include -----
+ "Populating demo database tables
+ prep_dbtab_for_eml( read_op = abap_true ).
+
+ DATA(tab_ro) = eml_read_root( key = 1 ).
+ out->write( tab_ro ).
+ out->write( |\n\n| ).
+
+ DATA(tab_ch) = eml_rba( key = 1 ).
+ out->write( tab_ch ).
+ out->write( |\n\n| ).
+
+ prep_dbtab_for_eml( ).
+
+ eml_modify_root(
+ EXPORTING
+ instances = VALUE #( ( %cid = `cid1` key_field = 3 field1 = 'aaa' field2 = 'bbb' field3 = 30 field4 = 300 )
+ ( %cid = `cid2` key_field = 4 field1 = 'ccc' field2 = 'ddd' field3 = 40 field4 = 400 ) )
+ IMPORTING
+ mapped = DATA(m)
+ failed = DATA(f)
+ reported = DATA(r)
+ ).
+
+ ASSERT f IS INITIAL.
+ ASSERT r IS INITIAL.
+ out->write( data = m-root name = `m-root` ).
+ out->write( |\n\n| ).
+
+ SELECT * FROM zdemo_abap_rapt1 INTO TABLE @DATA(itab).
+ out->write( data = itab name = `itab` ).
+ ENDMETHOD.
+
+ METHOD constructor.
+ "--------------------- 1a) ---------------------
+ "Demonstrating the constructor injection
+ "The parameter is only bound when you run the unit test.
+ "In that case, the test double is injected, and method calls
+ "in the implementations use data from the test double.
+ "Otherwise, a new instance is created that is used to call
+ "methods (a test double is not injected).
+ IF oref_constr_inj IS BOUND.
+ oref_data_provider = oref_constr_inj.
+ ELSE.
+ oref_data_provider = NEW #( ).
+ ENDIF.
+ ENDMETHOD.
+
+ METHOD td_constr_inj_calc_discount.
+ "--------------------- 1a) ---------------------
+ "Method that demonstrates the ABAP OO Test Double Framework and
+ "constructor injection in ABAP Unit.
+ "When running the unit test, 'oref_data_provider' includes the test
+ "double. The method expects a numeric value. 'get_discount' returns
+ "another numeric value on whose basis a discount calculation is
+ "performed. The value returned by the 'get_discount' method depends
+ "on the current weekday and the UTC time.
+ result = ( value * oref_data_provider->get_discount( ) ) / 100.
+ result = value - result.
+ ENDMETHOD.
+
+ METHOD td_param_inj_calc_discount.
+ "--------------------- 1b) ---------------------
+ "Method that demonstrates the ABAP OO Test Double Framework and
+ "parameter injection in ABAP Unit.
+ "When running the unit test, the optional parameter 'data_prov' is
+ "assigned, and therefore bound here. 'oref_data_provider' then includes
+ "the test double.
+ "The purpose of this method is similar to 'td_constr_inj_calc_discount'.
+ "Here, the method expects a date and time besides a numeric value.
+ IF data_prov IS BOUND.
+ oref_data_provider = data_prov.
+ ENDIF.
+
+ "Getting the weekday
+ DATA(day_value) = ( 5 + date MOD 7 ) MOD 7 + 1.
+ weekday = SWITCH #( day_value
+ WHEN 1 THEN `Monday`
+ WHEN 2 THEN `Tuesday`
+ WHEN 3 THEN `Wednesday`
+ WHEN 4 THEN `Thursday`
+ WHEN 5 THEN `Friday`
+ WHEN 6 THEN `Saturday`
+ WHEN 7 THEN `Sunday` ).
+
+ DATA(time_value) = COND #( WHEN time BETWEEN '000000' AND '045959' THEN 1 "Night discount
+ WHEN time BETWEEN '220000' AND '235959' THEN 1 "Night discount
+ WHEN time BETWEEN '050000' AND '115959' THEN 2 "Morning discount
+ WHEN time BETWEEN '180000' AND '215959' THEN 3 "Evening discount
+ ELSE 0 "No discount
+ ).
+
+ weekday = |{ weekday } ({ SWITCH #( time_value WHEN 1 THEN `night` WHEN 2 THEN `morning` WHEN 3 THEN `evening` ELSE `afternoon` ) })|.
+
+ DATA(disc) = oref_data_provider->get_discount_value( day_value = day_value time_value = time_value ).
+ result = ( value * disc ) / 100.
+ result = value - result.
+ message = |Original value: { value }; discount: { disc }; value with discount: { result }; | &&
+ |when: { weekday }, { date DATE = ISO }, { time TIME = ISO }|.
+ ENDMETHOD.
+
+ METHOD sql_get_shortest_flight_time.
+ "--------------------- 2) ---------------------
+ "When running the unit test, the DOC (database table) is replaced with a test double.
+
+ "Getting the shortest flight time among a given carrier
+ SELECT MIN( fltime ) AS fltime FROM zdemo_abap_flsch WHERE carrid = @carrier INTO @shortest_flight.
+ ENDMETHOD.
+
+ METHOD cds_get_data_set.
+ "--------------------- 3) ---------------------
+ "When running the unit test, the DOC (CDS view entity) is replaced with a test double.
+
+ "Getting a line filtered by the carrier
+ SELECT SINGLE * FROM zdemo_abap_cds_ve_agg_exp WHERE carrid = @carrier INTO @agg_line.
+ ENDMETHOD.
+
+ METHOD prep_dbtab_for_eml.
+ "Preparing database tables for ABAP EML statements
+ DELETE FROM zdemo_abap_rapt1.
+
+ IF read_op = abap_true.
+ DELETE FROM zdemo_abap_rapt2.
+ INSERT zdemo_abap_rapt1 FROM TABLE @( VALUE #( ( key_field = 1 field1 = 'aaa' field2 = 'bbb' field3 = 10 field4 = 100 ) ) ).
+ INSERT zdemo_abap_rapt2 FROM TABLE @( VALUE #( ( key_field = 1 key_ch = 11 field_ch1 = 'ccc' field_ch2 = 111 )
+ ( key_field = 1 key_ch = 12 field_ch1 = 'ddd' field_ch2 = 112 ) ) ).
+ ENDIF.
+ ENDMETHOD.
+
+ METHOD eml_read_root.
+ "--------------------- 4a) ---------------------
+ "Method that includes an ABAP EML read request on the root entity
+ "When running the unit test, the ABAP EML API is mocked.
+
+ READ ENTITIES OF zdemo_abap_rap_ro_m
+ ENTITY root
+ ALL FIELDS WITH VALUE #( ( key_field = key ) )
+ RESULT tab_ro.
+ ENDMETHOD.
+
+ METHOD eml_rba.
+ "--------------------- 4a) ---------------------
+ "Method that includes an ABAP EML read-by-assocation request
+ "When running the unit test, the ABAP EML API is mocked.
+
+ READ ENTITIES OF zdemo_abap_rap_ro_m
+ ENTITY root
+ BY \_child
+ ALL FIELDS WITH VALUE #( ( key_field = key ) )
+ RESULT tab_ch.
+ ENDMETHOD.
+
+ METHOD eml_modify_root.
+ "--------------------- 4a) ---------------------
+ "Method that includes an ABAP EML create request
+ "When running the unit test, the ABAP EML API is mocked.
+
+ MODIFY ENTITIES OF zdemo_abap_rap_ro_m
+ ENTITY root
+ CREATE
+ FIELDS ( key_field field1 field2 field3 field4 )
+ WITH instances
+ MAPPED mapped
+ FAILED failed
+ REPORTED reported.
+
+ COMMIT ENTITIES.
+ ENDMETHOD.
+
+ METHOD eml_read_root_buffer_td.
+ "--------------------- 4b) ---------------------
+ "Method that includes an ABAP EML read request
+ "When running the unit test, a transactional buffer test double is
+ "included.
+
+ READ ENTITIES OF zdemo_abap_rap_ro_u
+ ENTITY root
+ ALL FIELDS WITH VALUE #( ( key_field = key ) )
+ RESULT tab_ro_u.
+ ENDMETHOD.
+
+ENDCLASS.
+```
+
+1b) Insert the following code in the `Test Classes` tab (it is a local test class of `ZCL_DEMO_ABAP_UNIT_TDF`)
+
+```abap
+CLASS ltcl_test DEFINITION FINAL FOR TESTING
+ DURATION SHORT
+ RISK LEVEL HARMLESS.
+
+ PRIVATE SECTION.
+
+ "------------------- Local test class -------------------
+
+ "Object reference variable for class under test
+ CLASS-DATA cut TYPE REF TO zcl_demo_abap_unit_tdf.
+
+ "----- 1) ABAP OO Test Double Framework ------
+ "Test method that demonstrates constructor injection
+ METHODS test_tdf_constr_inj_get_discnt FOR TESTING.
+
+ "Test method that demonstrates parameter injection
+ METHODS test_tdf_param_inj_get_discnt FOR TESTING.
+
+ "----------- 2) ABAP SQL Test Double Framework -----------
+ METHODS test_sql_get_shortest_flight FOR TESTING RAISING cx_static_check.
+ CLASS-DATA sql_env TYPE REF TO if_osql_test_environment.
+
+ "----------- 3) ABAP CDS Test Double Framework-----------
+ METHODS test_select_cds FOR TESTING RAISING cx_static_check.
+ METHODS test_cds_standalone FOR TESTING RAISING cx_static_check.
+ METHODS prepare_testdata_set_cds.
+ CLASS-DATA cds_env TYPE REF TO if_cds_test_environment.
+ DATA zdemo_abap_fli_tab TYPE TABLE OF zdemo_abap_fli WITH EMPTY KEY.
+
+ "----------- 4) Managing dependencies on RAP business objects -----------
+ CONSTANTS entity TYPE abp_entity_name VALUE 'ZDEMO_ABAP_RAP_RO_M'.
+ CLASS-DATA eml_env TYPE REF TO if_botd_mockemlapi_bo_test_env.
+ "--- 4a) Test methods that demonstrate mocking EML APIs ---
+ METHODS test_eml_read_root FOR TESTING.
+ METHODS test_eml_rba FOR TESTING.
+ METHODS test_eml_modify_root FOR TESTING.
+ DATA td_eml TYPE REF TO if_botd_mockemlapi_test_double.
+
+ "--- 4b) Test method that demonstrates transactional buffer test doubles ---
+ METHODS test_eml_read_root_buffer_td FOR TESTING.
+
+ CLASS-DATA buffer_env TYPE REF TO if_botd_txbufdbl_bo_test_env.
+
+ "----------- Test fixture -----------
+ "Creating a common start state for each test method, clearing doubles
+ METHODS setup RAISING cx_static_check.
+ "Includes the creation of test environments
+ CLASS-METHODS class_setup RAISING cx_static_check.
+ "Clearing generated test doubles
+ CLASS-METHODS class_teardown.
+ENDCLASS.
+
+
+CLASS ltcl_test IMPLEMENTATION.
+
+ METHOD class_setup.
+ "Creating an instance for the class under test
+ cut = NEW zcl_demo_abap_unit_tdf( ).
+
+ "Creating instances of test environments for the test execution
+ "------ 2) SQL ------
+ sql_env = cl_osql_test_environment=>create(
+ i_dependency_list = VALUE #( ( 'zdemo_abap_flsch' ) ) ).
+
+ "------ 3) CDS ------
+ cds_env = cl_cds_test_environment=>create( i_for_entity = 'zdemo_abap_cds_ve_agg_exp'
+ test_associations = 'X' ).
+
+ "------ 4) ABAP EML ------
+ "4a) Mocking ABAP EML APIs
+ eml_env = cl_botd_mockemlapi_bo_test_env=>create(
+ environment_config = cl_botd_mockemlapi_bo_test_env=>prepare_environment_config(
+ )->set_bdef_dependencies( bdef_dependencies = VALUE #( ( 'ZDEMO_ABAP_RAP_RO_M' ) ) ) ).
+
+ "4b) Transactional buffer test doubles
+ buffer_env = cl_botd_txbufdbl_bo_test_env=>create(
+ environment_config = cl_botd_txbufdbl_bo_test_env=>prepare_environment_config(
+ )->set_bdef_dependencies( bdef_dependencies = VALUE #( ( 'ZDEMO_ABAP_RAP_RO_U' ) ) ) ).
+
+ ENDMETHOD.
+
+ METHOD class_teardown.
+ "Clearing generated test double at the end of the test execution
+ sql_env->destroy( ).
+ cds_env->destroy( ).
+ eml_env->destroy( ).
+ buffer_env->destroy( ).
+ ENDMETHOD.
+
+ METHOD setup.
+ sql_env->clear_doubles( ).
+ cds_env->clear_doubles( ).
+ eml_env->clear_doubles( ).
+ buffer_env->clear_doubles( ).
+ ENDMETHOD.
+
+ METHOD test_sql_get_shortest_flight.
+ "--- 2) ---
+ "Preparing and inserting test data
+ DATA test_data TYPE TABLE OF zdemo_abap_flsch WITH EMPTY KEY.
+ test_data = VALUE #(
+ ( carrid = 'LH'
+ connid = 0401
+ fltime = 435 )
+ ( carrid = 'LH'
+ connid = 0402
+ fltime = 455 )
+ ( carrid = 'LH'
+ connid = 2402
+ fltime = 65 ) ).
+
+ sql_env->insert_test_data( test_data ).
+
+ "Calling method of the class under test
+ DATA carrier TYPE zdemo_abap_flsch-carrid VALUE 'LH'.
+ DATA(result) = cut->sql_get_shortest_flight_time( carrier ).
+
+ "Verifying result
+ cl_abap_unit_assert=>assert_equals(
+ act = result
+ exp = 65
+ msg = `Not the shortest flight`
+ quit = if_abap_unit_constant=>quit-no ).
+ ENDMETHOD.
+
+ METHOD prepare_testdata_set_cds.
+ "Preparing and inserting test data
+ zdemo_abap_fli_tab = VALUE #(
+ ( carrid = 'XX'
+ connid = 0407
+ fldate = '20231128'
+ price = '1102.77'
+ currency = 'JPY'
+ planetype = 'A380-800'
+ seatsmax = 475
+ seatsocc = 458
+ paymentsum = '563231.65'
+ seatsmax_b = 30
+ seatsocc_b = 27
+ seatsmax_f = 20
+ seatsocc_f = 20 )
+ ( carrid = 'XX'
+ connid = 0407
+ fldate = '20231019'
+ price = '1102.77'
+ currency = 'JPY'
+ planetype = 'A380-800'
+ seatsmax = 475
+ seatsocc = 452
+ paymentsum = '553552.12'
+ seatsmax_b = 30
+ seatsocc_b = 28
+ seatsmax_f = 20
+ seatsocc_f = 19 )
+ ( carrid = 'XX'
+ connid = 0408
+ fldate = '20231128'
+ price = '1102.77'
+ currency = 'JPY'
+ planetype = '747-400'
+ seatsmax = 385
+ seatsocc = 365
+ paymentsum = '470129.20'
+ seatsmax_b = 31
+ seatsocc_b = 28
+ seatsmax_f = 21
+ seatsocc_f = 20 )
+ ( carrid = 'XX'
+ connid = 0408
+ fldate = '20230123'
+ price = '1102.77'
+ currency = 'JPY'
+ planetype = '747-400'
+ seatsmax = 385
+ seatsocc = 372
+ paymentsum = '487715.90'
+ seatsmax_b = 31
+ seatsocc_b = 31
+ seatsmax_f = 21
+ seatsocc_f = 20 ) ).
+
+ cds_env->insert_test_data( i_data = zdemo_abap_fli_tab ).
+ ENDMETHOD.
+
+ METHOD test_select_cds.
+ "--- 3) ---
+ "Preparing and inserting test data
+ prepare_testdata_set_cds( ).
+
+ "Calling method of the class under test
+ DATA carrier TYPE zdemo_abap_fli-carrid VALUE 'XX'.
+ DATA(act_result) = cut->cds_get_data_set( carrier ).
+
+ "Verifying result
+ cl_abap_unit_assert=>assert_equals(
+ act = act_result
+ exp = VALUE zdemo_abap_cds_ve_agg_exp( carrid = 'XX' currency = 'JPY' avg_seats_occ = '411.75'
+ avg_paysum = '518657.22' total_paysum = '2074628.87'
+ min_occ_seats = 365 max_occ_seats = 458
+ max_occ_seats_all = 458 cnt = 4 cnt_planetype = 2 )
+ msg = `The values do not match the expected result`
+ quit = if_abap_unit_constant=>quit-no ).
+ ENDMETHOD.
+
+ METHOD test_eml_read_root.
+ "--- 4a) ---
+ "Mocking ABAP EML API (read request)
+
+ "Preparing test data (RAP BO instances)
+ DATA read_tab_ro_import TYPE TABLE FOR READ IMPORT zdemo_abap_rap_ro_m.
+ DATA read_tab_ro_result TYPE TABLE FOR READ RESULT zdemo_abap_rap_ro_m.
+ read_tab_ro_import = VALUE #( ( key_field = 2 ) ).
+ read_tab_ro_result = VALUE #( ( key_field = 2 field1 = 'uuu' field2 = 'vvv' field3 = 20 field4 = 200 ) ).
+
+ "Configuring input and output for the ABAP EML read request
+ "Creating input/output configuration builders
+ DATA(input_config_read) = cl_botd_mockemlapi_bldrfactory=>get_input_config_builder( )->for_read( ).
+ DATA(output_config_read) = cl_botd_mockemlapi_bldrfactory=>get_output_config_builder( )->for_read( ).
+
+ "Creating input for the entities (here, only one entity is included)
+ "Instead of the entity name, an alias name is also accepted.
+ DATA(eml_read_input) = input_config_read->build_entity_part( entity
+ )->set_instances_for_read( read_tab_ro_import ).
+
+ "Input configuration
+ DATA(input) = input_config_read->build_input_for_eml( )->add_entity_part( eml_read_input ).
+
+ "Output configuration
+ DATA(output) = output_config_read->build_output_for_eml( )->set_result_for_read( read_tab_ro_result ).
+
+ "Configuring the RAP BO test double
+ td_eml = eml_env->get_test_double( entity ).
+ td_eml->configure_call( )->for_read( )->when_input( input )->then_set_output( output ).
+
+ "Calling method (containing the ABAP EML read request) of the class under test
+ DATA(t_read_res_ro) = cut->eml_read_root( key = 2 ).
+
+ "Verifying the result
+ cl_abap_unit_assert=>assert_equals(
+ act = lines( t_read_res_ro )
+ exp = 1
+ msg = `The number of lines does not match`
+ quit = if_abap_unit_constant=>quit-no ).
+
+ TYPES struc_read_ro TYPE STRUCTURE FOR READ RESULT zdemo_abap_rap_ro_m.
+ cl_abap_unit_assert=>assert_equals(
+ act = t_read_res_ro[ 1 ]
+ exp = VALUE struc_read_ro( key_field = 2 field1 = 'uuu' field2 = 'vvv' field3 = 20 field4 = 200 )
+ msg = `The values do not match the expected result`
+ quit = if_abap_unit_constant=>quit-no ).
+ ENDMETHOD.
+
+ METHOD test_tdf_constr_inj_get_discnt.
+ "--- 1a) ---
+ "Method that demonstrates the ABAP OO Test Double Framework
+ "Injection mechanism: Constructor injection
+
+ "Creating a test double
+ DATA(test_double) = CAST zcl_demo_abap_unit_dataprov(
+ cl_abap_testdouble=>create( 'zcl_demo_abap_unit_dataprov' ) ).
+
+ "Configuring a method call
+ "It is used for the next method call (the actual method calling)
+ "In this example, the method only has a returning parameter. Check
+ "the class documentation for more static methods of the cl_abap_testdouble
+ "class, e.g. for configuring other parameters.
+ cl_abap_testdouble=>configure_call( test_double
+ )->returning( 5 ).
+
+ "Calling method of the external class
+ test_double->get_discount( ).
+
+ "Injecting the test double
+ "Here, the instance constructor is provided with the test double
+ "instance
+ cut = NEW #( test_double ).
+
+ "Calling method of the class under test
+ DATA(result) = cut->td_constr_inj_calc_discount( 500 ).
+
+ "Verifying the result
+ cl_abap_unit_assert=>assert_equals(
+ act = result
+ exp = 475
+ msg = `The values do not match the expected result`
+ quit = if_abap_unit_constant=>quit-no ).
+ ENDMETHOD.
+
+ METHOD test_tdf_param_inj_get_discnt.
+ "--- 1b) ---
+ "Method that demonstrates the ABAP OO Test Double Framework
+ "Injection mechanism: Parameter injection
+
+ "Creating a test double
+ DATA(test_double_param_inj) = CAST zcl_demo_abap_unit_dataprov(
+ cl_abap_testdouble=>create( 'zcl_demo_abap_unit_dataprov' ) ).
+
+ "Configuring a method call, ignoring importing parameters
+ cl_abap_testdouble=>configure_call( test_double_param_inj
+ )->ignore_parameter( 'DAY_VALUE'
+ )->ignore_parameter( 'TIME_VALUE'
+ )->returning( 40 ).
+
+ "Calling method of the external class
+ "Dummy values are provided for the non-optional importing
+ "parameters (the returning value is determined above)
+ test_double_param_inj->get_discount_value(
+ EXPORTING
+ day_value = 99
+ time_value = 99
+ ).
+
+ "Calling method of the class under test
+ "In this case, the optional parameter is assigned the
+ "test double replacing the DOC
+ cut->td_param_inj_calc_discount(
+ EXPORTING
+ value = 100
+ date = '20241201' "Sunday
+ time = '100000'
+ data_prov = test_double_param_inj
+ IMPORTING
+ message = DATA(msg)
+ weekday = DATA(weekday)
+ RECEIVING
+ result = DATA(result)
+ ).
+
+ "Verifying the result
+ cl_abap_unit_assert=>assert_equals(
+ act = result
+ exp = 60
+ msg = `The values do not match the expected result`
+ quit = if_abap_unit_constant=>quit-no ).
+ ENDMETHOD.
+
+ METHOD test_eml_rba.
+ "--- 4a) ---
+ "Method that demonstrates mocking an ABAP EML API (read-by-association request)
+
+ "Preparing test data (RAP BO instances)
+ DATA read_tab_ch_import TYPE TABLE FOR READ IMPORT zdemo_abap_rap_ro_m\_child.
+ DATA read_tab_ch_result TYPE TABLE FOR READ RESULT zdemo_abap_rap_ro_m\_child.
+
+ read_tab_ch_import = VALUE #( ( key_field = 2 ) ).
+
+ read_tab_ch_result = VALUE #( ( key_field = 2 key_ch = 22 field_ch1 = 'www' field_ch2 = 222 )
+ ( key_field = 2 key_ch = 23 field_ch1 = 'xxx' field_ch2 = 223 ) ).
+
+ "Configuring input and output for the ABAP EML read-by-association request
+ DATA(input_config_rba) = cl_botd_mockemlapi_bldrfactory=>get_input_config_builder( )->for_read( ).
+ DATA(output_config_rba) = cl_botd_mockemlapi_bldrfactory=>get_output_config_builder( )->for_read( ).
+
+ "Creating input for the entities (here, only one entity is included)
+ DATA(eml_rba_input) = input_config_rba->build_entity_part( entity
+ )->set_instances_for_read_ba( read_tab_ch_import ).
+
+ "Input configuration
+ DATA(input_rba) = input_config_rba->build_input_for_eml( )->add_entity_part( eml_rba_input ).
+
+ "Output configuration
+ DATA(output_rba) = output_config_rba->build_output_for_eml(
+ )->set_result_for_read_ba(
+ source_entity_name = entity
+ assoc_name = '_CHILD'
+ result = read_tab_ch_result ).
+
+ "Configuring the RAP BO test double
+ td_eml = eml_env->get_test_double( entity ).
+ td_eml->configure_call( )->for_read( )->when_input( input_rba )->then_set_output( output_rba ).
+
+ "Calling method (containing the ABAP EML read-by-association request)
+ "of the class under test
+ DATA(t_read_res_ch) = cut->eml_rba( key = 2 ).
+
+ "Verifying the result
+ cl_abap_unit_assert=>assert_equals(
+ act = lines( t_read_res_ch )
+ exp = 2
+ msg = `The number of lines does not match`
+ quit = if_abap_unit_constant=>quit-no ).
+
+ TYPES struc_read_ch TYPE STRUCTURE FOR READ RESULT zdemo_abap_rap_ro_m\_child.
+ cl_abap_unit_assert=>assert_equals(
+ act = t_read_res_ch[ 2 ]
+ exp = VALUE struc_read_ch( key_field = 2 key_ch = 23 field_ch1 = 'xxx' field_ch2 = 223 )
+ msg = `The values do not match the expected result`
+ quit = if_abap_unit_constant=>quit-no ).
+ ENDMETHOD.
+
+
+ METHOD test_eml_modify_root.
+ "--- 4a) ---
+ "Method that demonstrates mocking an ABAP EML API (create request)
+
+ "Data object delcarations used in the test method
+ DATA create_tab_ro TYPE TABLE FOR CREATE zdemo_abap_rap_ro_m.
+ DATA mapped_resp TYPE RESPONSE FOR MAPPED EARLY zdemo_abap_rap_ro_m.
+ DATA failed_resp TYPE RESPONSE FOR FAILED EARLY zdemo_abap_rap_ro_m.
+ DATA reported_resp TYPE RESPONSE FOR REPORTED EARLY zdemo_abap_rap_ro_m.
+
+ "Preparing test data (RAP BO instances, response parameters)
+ create_tab_ro = VALUE #( ( %cid = `cid1` key_field = 3 field1 = 'aaa' field2 = 'bbb' field3 = 30 field4 = 300 )
+ ( %cid = `cid2` key_field = 4 field1 = 'ccc' field2 = 'ddd' field3 = 40 field4 = 400 ) ).
+
+ mapped_resp-root = VALUE #( ( %cid = 'cid1' key_field = 3 ) ( %cid = 'cid2' key_field = 4 ) ).
+ failed_resp-root = VALUE #( ).
+ reported_resp-root = VALUE #( ).
+
+ "Configuring input and output for the ABAP EML create request
+ DATA(input_config_modify) = cl_botd_mockemlapi_bldrfactory=>get_input_config_builder( )->for_modify( ).
+ DATA(output_config_modify) = cl_botd_mockemlapi_bldrfactory=>get_output_config_builder( )->for_modify( ).
+
+ "Creating input for the entities (here, only one entity is included)
+ DATA(eml_modify_input) = input_config_modify->build_entity_part( entity
+ )->set_instances_for_create( create_tab_ro ).
+
+ "Input configuration
+ DATA(input) = input_config_modify->build_input_for_eml( )->add_entity_part( eml_modify_input ).
+
+ "Output configuration
+ DATA(output) = output_config_modify->build_output_for_eml( )->set_mapped( mapped_resp
+ )->set_failed( failed_resp )->set_reported( reported_resp ).
+
+ "Configuring the RAP BO test double
+ td_eml = eml_env->get_test_double( entity ).
+ td_eml->configure_call( )->for_modify( )->when_input( input )->then_set_output( output ).
+
+ "Calling method (containing the ABAP EML read request) of the class under test
+ cut->eml_modify_root(
+ EXPORTING
+ instances = VALUE #( ( %cid = `cid1` key_field = 3 field1 = 'aaa' field2 = 'bbb' field3 = 30 field4 = 300 )
+ ( %cid = `cid2` key_field = 4 field1 = 'ccc' field2 = 'ddd' field3 = 40 field4 = 400 ) )
+ IMPORTING
+ mapped = DATA(m)
+ failed = DATA(f)
+ reported = DATA(r)
+ ).
+
+ "Verifying the result
+ cl_abap_unit_assert=>assert_equals(
+ act = lines( m-root )
+ exp = 2
+ msg = `The number of lines does not match`
+ quit = if_abap_unit_constant=>quit-no ).
+
+ cl_abap_unit_assert=>assert_equals(
+ act = lines( f-root )
+ exp = 0
+ msg = `The number of lines does not match`
+ quit = if_abap_unit_constant=>quit-no ).
+
+ cl_abap_unit_assert=>assert_equals(
+ act = lines( r-root )
+ exp = 0
+ msg = `The number of lines does not match`
+ quit = if_abap_unit_constant=>quit-no ).
+
+ ENDMETHOD.
+
+ METHOD test_eml_read_root_buffer_td.
+ "--- 4b) ---
+ "Method that demonstrates transactional buffer test doubles
+
+ "Creating test double
+ "Note: You have more configuration options. Check, for example, the
+ "methods configure_additional_behavior, set_fields_handler methods, etc.
+ "as described in the documentation.
+ DATA(double) = buffer_env->get_test_double( 'zdemo_abap_rap_ro_u' ).
+
+ "Preparing test data
+ "Populating the transactional buffer with an ABAP EML create request
+ MODIFY ENTITIES OF zdemo_abap_rap_ro_u
+ ENTITY root
+ CREATE FIELDS ( key_field field1 field2 field3 field4 )
+ WITH VALUE #( ( %cid = `cid5` key_field = 5 field1 = 'eee' )
+ ( %cid = `cid6` key_field = 6 field1 = 'fff' ) )
+ REPORTED DATA(reported)
+ FAILED DATA(failed)
+ MAPPED DATA(mapped).
+
+ "Calling method (containing an ABAP EML read request) of the class under test
+ cut->eml_read_root_buffer_td(
+ EXPORTING
+ key = 5
+ RECEIVING
+ tab_ro_u = DATA(read_result) ).
+
+ "Verifying the result
+ cl_abap_unit_assert=>assert_equals(
+ act = read_result[ 1 ]-key_field
+ exp = 5
+ msg = `The value does not match the expected result`
+ quit = if_abap_unit_constant=>quit-no ).
+
+ cl_abap_unit_assert=>assert_equals(
+ act = read_result[ 1 ]-field1
+ exp = 'eee'
+ msg = `The value does not match the expected result`
+ quit = if_abap_unit_constant=>quit-no ).
+
+ "Another calling of the method of the class under test (non-existent instance
+ "in the transactional buffer test double)
+ cut->eml_read_root_buffer_td(
+ EXPORTING
+ key = 1
+ RECEIVING
+ tab_ro_u = DATA(read_result_f) ).
+
+ cl_abap_unit_assert=>assert_initial(
+ EXPORTING
+ act = read_result_f
+ msg = `An instance was found`
+ quit = if_abap_unit_constant=>quit-no ).
+
+ ENDMETHOD.
+
+ METHOD test_cds_standalone.
+ "--- 3) ---
+ "Method that tests the implementation logic of a CDS view entity
+ "Unlike the 'test_select_cds' method, this method does not call a method
+ "in the class under test.
+
+ "Preparing test data
+ prepare_testdata_set_cds( ).
+
+ "Retrieving data from the CDS view entity (test data is used here)
+ SELECT * FROM zdemo_abap_cds_ve_agg_exp INTO TABLE @DATA(result).
+
+ "Verifying the result
+ cl_abap_unit_assert=>assert_equals(
+ act = result[ 1 ]-carrid
+ exp = 'XX'
+ msg = `The value does not match the expected result`
+ quit = if_abap_unit_constant=>quit-no ).
+
+ cl_abap_unit_assert=>assert_equals(
+ act = result[ 1 ]-avg_seats_occ
+ exp = '411.75'
+ msg = `The value does not match the expected result`
+ quit = if_abap_unit_constant=>quit-no ).
+
+ cl_abap_unit_assert=>assert_equals(
+ act = result[ 1 ]-max_occ_seats
+ exp = 458
+ msg = `The value does not match the expected result`
+ quit = if_abap_unit_constant=>quit-no ).
+ ENDMETHOD.
+
+ENDCLASS.
+```
+
+**Class 2 `ZCL_DEMO_ABAP_UNIT_DATAPROV` (class representing a DOC)**
+
+Insert the following code in the `Global Class` tab
+
+```abap
+CLASS zcl_demo_abap_unit_dataprov DEFINITION
+ PUBLIC
+ CREATE PUBLIC .
+
+ PUBLIC SECTION.
+
+* -------------------------- NOTE ----------------------------------
+* This is an example class that represents a dependent-on-component
+* (DOC). Methods of this class are called in another class. There,
+* the DOCs are replaced by test doubles when running ABAP Unit tests.
+
+ METHODS get_discount RETURNING VALUE(discount) TYPE decfloat34.
+ METHODS get_discount_value IMPORTING day_value TYPE i
+ time_value TYPE i
+ RETURNING VALUE(discount_value) TYPE decfloat34.
+ PROTECTED SECTION.
+ PRIVATE SECTION.
+ENDCLASS.
+
+
+
+CLASS zcl_demo_abap_unit_dataprov IMPLEMENTATION.
+ METHOD get_discount.
+ "Getting the weekday
+ "1) Monday, 2) Tuesday, 3) Wednesday, 4) Thursday, 5) Friday, 6) Saturday, 7) Sunday
+ DATA(weekday) = ( 5 + CONV d( xco_cp=>sy->date( xco_cp_time=>time_zone->utc
+ )->as( xco_cp_time=>format->iso_8601_basic )->value ) MOD 7 ) MOD 7 + 1.
+
+ "- Standard discount is granted at the weekend (Saturday, Sunday)
+ "- On other weekdays, discount is granted depending on the daytime
+ IF weekday = 6 OR weekday = 7.
+ discount = '20'.
+ ELSE.
+ "Retrieving the current time in UTC
+ DATA(utc_time) = CONV t( xco_cp=>sy->time( xco_cp_time=>time_zone->utc
+ )->as( xco_cp_time=>format->iso_8601_basic )->value ).
+
+ discount = COND #( WHEN utc_time BETWEEN '000000' AND '045959' THEN '15' "Night discount
+ WHEN utc_time BETWEEN '220000' AND '235959' THEN '15' "Night discount
+ WHEN utc_time BETWEEN '050000' AND '115959' THEN '10' "Morning discount
+ WHEN utc_time BETWEEN '180000' AND '215959' THEN '5' "Evening discount
+ ELSE 0 "No discount
+ ).
+ ENDIF.
+ ENDMETHOD.
+
+ METHOD get_discount_value.
+ CASE day_value.
+ "Standard discount is granted at the weekend (Saturday, Sunday)
+ WHEN 6 OR 7.
+ discount_value = '20'.
+ "On other weekdays, discount is granted depending on the daytime
+ WHEN OTHERS.
+ discount_value = SWITCH #( time_value
+ WHEN 1 THEN '15' "Night discount
+ WHEN 2 THEN '10' "Morning discount
+ WHEN 3 THEN '5' "Evening discount
+ ELSE '0' "No discount
+ ).
+ ENDCASE.
+ ENDMETHOD.
+
+ENDCLASS.
+```
+
+
+
+
+⬆️ back to top
+
## Running and Evaluating ABAP Unit Tests
There are many ways to run ABAP unit tests as described [here](https://help.sap.com/docs/ABAP_PLATFORM_NEW/c238d694b825421f940829321ffa326a/4ec4c6c66e391014adc9fffe4e204223.html?locale=en-US).
@@ -505,8 +1680,7 @@ For more information about evaluating ABAP unit test results, see [here](https:/
- SAP Help Portal:
- [Unit Testing with ABAP Unit](https://help.sap.com/docs/ABAP_PLATFORM_NEW/c238d694b825421f940829321ffa326a/08c60b52cb85444ea3069779274b43db.html?locale=en-US)
- [ABAP Unit](https://help.sap.com/docs/ABAP_PLATFORM_NEW/ba879a6e2ea04d9bb94c7ccd7cdac446/491cfd8926bc14cde10000000a42189b.html?locale=en-US)
- - The [ABAP OO Test Double Framework](https://help.sap.com/docs/ABAP_PLATFORM_NEW/c238d694b825421f940829321ffa326a/804c251e9c19426cadd1395978d3f17b.html?locale=en-US) based on class `CL_ABAP_TESTDOUBLE` simplifies and standardizes the creation and configuration of test doubles.
- - Development Guide for the ABAP RESTful Application Programming Model: [Testing different artifacts of RAP business objects and related OData services](https://help.sap.com/docs/btp/sap-abap-restful-application-programming-model/test?locale=en-US)
+
⬆️ back to top
diff --git a/22_Misc_ABAP_Classes.md b/22_Misc_ABAP_Classes.md
index ffde55c..c3358e2 100644
--- a/22_Misc_ABAP_Classes.md
+++ b/22_Misc_ABAP_Classes.md
@@ -31,14 +31,14 @@
- [Calling Services](#calling-services)
- [Reading and Writing XLSX Content](#reading-and-writing-xlsx-content)
- [Zip Files](#zip-files)
+ - [ABAP Unit](#abap-unit)
-This ABAP cheat sheet contains a selection of available ABAP classes, serving as a quick introduction, along with code snippets to explore the functionality in action.
+This ABAP cheat sheet contains a selection of [released](https://help.sap.com/doc/abapdocu_cp_index_htm/CLOUD/en-US/index.htm?file=abenreleased_api_glosry.htm) ABAP classes that are available in [ABAP for Cloud Development](https://help.sap.com/doc/abapdocu_cp_index_htm/CLOUD/en-US/index.htm?file=abenabap_for_cloud_dev_glosry.htm). It serves as a quick introduction, along with code snippets to explore the functionality in action.
> **💡 Note**
> - The cheat sheet is not a comprehensive overview, and the code snippets do not claim to be comprehensive as far as options, methods, or parameters are concerned. It is intended to give you a rough overview, for you to get an idea. It is an invitation to a more in-depth exploration.
> - For more information and where available, refer to the class documentation (for example, choose F2 when the cursor is on the class name in ADT), the ABAP Keyword Documentation, and the SAP Help Portal documentation.
-> - In the cheat sheet, the focus is on a selected set of classes that are available in [ABAP for Cloud Development](https://help.sap.com/doc/abapdocu_cp_index_htm/CLOUD/en-US/index.htm?file=abenabap_for_cloud_dev_glosry.htm).
> - [Disclaimer](./README.md#%EF%B8%8F-disclaimer)
## Excursion: Available Classes in ABAP for Cloud Development
@@ -3233,7 +3233,7 @@ ENDCLASS.
CL_ABAP_ZIP |
-The following example creates a zip file and adds three txt files with sample content.
+The following example creates a zip file and adds three txt files with sample content. Note that the example snippet for `CL_WEB_HTTP_CLIENT_MANAGER` and `CL_HTTP_DESTINATION_PROVIDER` (calling services) also includes the use of `CL_ABAP_ZIP`.
```abap
"Create zip file
@@ -3261,4 +3261,65 @@ DATA(zipped_file) = zip->save( ).
+ ⬆️ back to top
+
+## ABAP Unit
+
+
+
+| Class | Details/Code Snippet |
+
+
+ CL_ABAP_UNIT_ASSERT |
+
+
+Provides methods to verify test expectations in ABAP Unit tests. For more information, see the [ABAP Unit Tests](14_ABAP_Unit_Tests.md) cheat sheet.
+
+
+```abap
+"Code in a test class
+
+...
+
+DATA(result) = 100.
+
+...
+
+cl_abap_unit_assert=>assert_equals(
+ act = result
+ exp = 100
+ msg = `The value does not match the expected result`
+ quit = if_abap_unit_constant=>quit-no ).
+
+ENDDO.
+
+DATA(zipped_file) = zip->save( ).
+```
+
+ |
+
+
+ CL_ABAP_TESTDOUBLE
CL_OSQL_TEST_ENVIRONMENT
CL_CDS_TEST_ENVIRONMENT
CL_BOTD_TXBUFDBL_BO_TEST_ENV
CL_BOTD_MOCKEMLAPI_BO_TEST_ENV |
+
+
+
+- The classes can be used in the context of ABAP Unit to create test doubles in a standardized way.
+- The test doubles replace dependent-on components (DOC) during unit tests.
+- DOCs:
+ - Classes and interfaces:
+ - `CL_ABAP_TESTDOUBLE`: ABAP OO Test Double Framework
+ - Database (e.g. database tables or CDS view entities)
+ - `CL_OSQL_TEST_ENVIRONMENT`: ABAP SQL Test Double Framework; to test ABAP SQL statements that depend on data sources such as database tables or CDS view entities
+ - `CL_CDS_TEST_ENVIRONMENT`: ABAP CDS Test Double Framework; to test logic implemented in CDS entities
+ - RAP business objects
+ - `CL_BOTD_TXBUFDBL_BO_TEST_ENV`: Creating transactional buffer test doubles
+ - `CL_BOTD_MOCKEMLAPI_BO_TEST_ENV`: Mocking ABAP EML APIs
+
+- Note that more classes are available for other use cases.
+- For more information, see the [ABAP Unit Tests](14_ABAP_Unit_Tests.md) cheat sheet and the [documentation](https://help.sap.com/docs/abap-cloud/abap-development-tools-user-guide/managing-dependencies-with-abap-unit).
+
+ |
+
+
+
⬆️ back to top
\ No newline at end of file
|