PackagePackagetypeA library you install and call yourself.See every one →What this type is →independently maintainedindependently maintainedoriginPublished by one person or a small company outside SAP. Says nothing about quality; the readings say that.See every one →

ABAP Test Kit

A library that puts a fluent API on SAP's ABAP Test Double Framework: dummy, stub, spy and mock doubles of a global interface from one facade class, written against the ABAP for Cloud Development language version.

Configuring a double with SAP's ABAP Test Double Framework takes two statements, configure_call( ) and a call that looks like a real call but only records its arguments, and a call that matches no configuration returns initial values without saying so. This keeps that engine and puts a fluent API in front of it: stub( ) answers through when( )->with( )->returns( ), spy( ) records calls so the checks are written after the act step as was_called( )->with( )->times( ), mock( ) declares every call up front and fails at an undeclared one, and dummy( ) fills a collaborator the scenario never calls. Method names, parameter names and values are checked against the doubled type while the rule is written, and every mistake surfaces through one exception class, ZCX_ATK, whose text carries what went wrong, how to fix it, and the facts — the arguments, and the method and line of the code under test that made the call, read from the XCO call stack. It doubles global interfaces only, and ZCL_ATK is declared FOR TESTING, so production code cannot depend on it.

Facts

Last pushLicenceMIT

Where it sits

Lives in
Provides
Unit testingABAPUnit testingcapabilityTesting application logic without a running business process — and the test doubles that make dependencies on the database and on framework services tractable.
Add-on to
ABAP Test Double FrameworkABAP Test Double FrameworkartifactThe test double framework delivered with the ABAP platform: CL_ABAP_TESTDOUBLE creates a double of a global interface or class, configures what each call answers, and verifies the calls made on it.
Flavors
Requires
ABAP Test Double FrameworkABAP Test Double FrameworkartifactCreates the doubles and intercepts the calls; this library adds the fluent configuration, the checks and the messages on top, and names the framework under Prerequisites.abapGitabapGitartifactInstalls the library into the system: the README gives abapGit as the installation route, into a package flagged as ABAP Cloud in the customer namespace, with two sub-packages created in ADT before the pull.

Who is behind it

George DrakosGeorge DrakosPersonMaintains this
In the atlas: maintains 5, contributes to 8
@greltel
maintainer

Before choosing this

Conditions

PracticalFunctional boundary

Classes cannot be doubled: ZCL_ATK takes the name of a global interface, and a class is rejected with a message that says to extract an interface or to double the class with CL_ABAP_TESTDOUBLE directly; local interfaces of a test include cannot be named either. Static methods and events of the doubled interface are not doubled, and with( ) compares for equality, with matchers listed as planned. Below SAP S/4HANA 2023, or outside the ABAP for Cloud Development language version, the prerequisites the README names are not met.

What it declares

Every artifact

Type
Package
Origin
Independently maintained

As a package

Where it runs
does not say
Distributed through
does not say

Connections3

README

quoted from greltel/abap-test-kit under MIT, read
greltel/abap-test-kit/README.mdOn GitHub

ABAP Test Kit

ABAP Cloud ABAP Version Code Statistics License Release abaplint unit tests

Table of contents

  1. ABAP Test Kit
  2. Prerequisites
  3. Installation
  4. Quick start
  5. Versioning
  6. License
  7. Contributors-Developers
  8. Available Test Doubles
  9. Before and After
  10. Limitations
  11. Design Goals-Features
  12. To-Do

ABAP Test Kit

A small library that makes ABAP Unit tests with test doubles shorter and easier to read, for development on SAP S/4HANA and SAP BTP ABAP Environment.

It is built on top of SAP's ABAP Test Double Framework (CL_ABAP_TESTDOUBLE): the framework still creates the doubles and intercepts the calls, the library adds a fluent API to configure and check them, and messages that say what went wrong and how to fix it. It is meant both for developers who write their first unit test and for those who want their tests shorter and more descriptive.

The library is written against the ABAP for Cloud Development language version, follows Clean Core principles, and consumes released APIs only.

Prerequisites

  • SAP S/4HANA 2023 (or higher) OR SAP BTP ABAP Environment
  • ABAP language version: ABAP for Cloud Development
  • ABAP Test Double Framework (CL_ABAP_TESTDOUBLE)
  • XCO library with the call stack API (XCO_CP_CALL_STACK), which names the method and line that called a double in failure messages
  • Statement compatibility from v758 and Cloud

Verified with the ABAP Cloud Developer Trial 2025 (SAP S/4HANA 2023 based) and, on every push, off-stack with the abaplint transpiler. Reports from SAP BTP ABAP Environment and S/4HANA Cloud Public Edition are welcome.

Installation

Install via abapGit into a package flagged as ABAP Cloud in the customer namespace, e.g. ZATK. The repository is set to the ABAP language version ABAP for Cloud Development, so abapGit only imports it into packages with that language version. Create the package and its two sub-packages in ADT before the pull:

PackageSuper packageContent
ZATK-The library: ZCL_ATK, the ZIF_ATK_* interfaces, ZCX_ATK and message class ZATK
ZATK_TESTZATKFixtures for the unit tests of the library
ZATK_DEMOZATKA small order service, tested once with the classic framework and once with the library

Then link https://github.com/greltel/abap-test-kit.git to ZATK, pull, and run the unit tests of ZATK and its sub-packages; all of them should pass. ZCL_ATK is a test class (FOR TESTING), so only test code can use it.

Quick start

The code under test gets its collaborators through the constructor, as interfaces. The test replaces them with doubles from ZCL_ATK and injects instance( ) of each double:

CLASS ltc_order_service DEFINITION FINAL FOR TESTING RISK LEVEL HARMLESS DURATION SHORT.
  PRIVATE SECTION.
    DATA repository TYPE REF TO zif_atk_stub.
    DATA audit_log TYPE REF TO zif_atk_spy.
    DATA cut TYPE REF TO zif_atk_demo_order_service.

    METHODS setup.
    METHODS when_cancel_then_logged_once FOR TESTING RAISING cx_static_check.
ENDCLASS.


CLASS ltc_order_service IMPLEMENTATION.

  METHOD setup.
    repository = zcl_atk=>stub( 'ZIF_ATK_DEMO_ORDER_REPO' ).
    audit_log = zcl_atk=>spy( 'ZIF_ATK_DEMO_AUDIT_LOG' ).
    cut = NEW zcl_atk_demo_order_service( repository = CAST #( repository->instance( ) )
                                          audit_log  = CAST #( audit_log->instance( ) ) ).
  ENDMETHOD.

  METHOD when_cancel_then_logged_once.
    " arrange: the repository answers with an open order
    repository->when( 'GET_ORDER' )->returns( VALUE zif_atk_demo_order_repo=>ty_order(
        id     = '0000004711'
        status = zif_atk_demo_order_repo=>status-open ) ).

    " act
    cut->cancel( '4711' ).

    " assert: the cancellation was written to the log, exactly once
    audit_log->was_called( 'WRITE' )->with( parameter = 'ACTION' value = `CANCELLED` )->times( 1 ).
  ENDMETHOD.

ENDCLASS.

Run it in ADT with Ctrl+Shift+F10. If a name is misspelled or a value does not fit the parameter, the test fails at that line with a message that says what to change.

Which double do I need?

The collaborator...UseBecause
must be passed in, but the scenario never uses itdummy( )any call fails the test and names the method
is asked for datastub( )the test decides the answers, and checks the result of the code under test
is told to do somethingspy( )the test checks after the act step that it was told, and how often
must receive exactly the declared calls and nothing elsemock( )any other call fails at once; verify( ) reports missing calls

If unsure between spy and mock, take the spy: it answers like a stub, and the checks are written after the act step, where a reader expects them.

Versioning

Releases are tagged vMAJOR.MINOR.PATCH and listed in CHANGELOG.md, which also carries the changes on main that are not released yet under Unreleased. One version covers the whole repository: in abapGit open the repository, choose Switch tag and pick the release; Switch branch to main follows the latest state. The rules for what bumps which part of the version are in CONTRIBUTING.md.

License

This project is licensed under the MIT License.

Contributors-Developers

The repository was created by George Drakos.

Available Test Doubles

Object names follow ZCL_ATK / ZIF_ATK_* / ZCX_ATK.

DoubleEntry pointInterfaceDescription
Dummyzcl_atk=>dummy( )ZIF_ATK_DUMMYFills a parameter of the code under test that the scenario never uses; any call fails the test
Stubzcl_atk=>stub( )ZIF_ATK_STUBAnswers calls with the values, output parameters or exceptions the test defines
Spyzcl_atk=>spy( )ZIF_ATK_SPYA stub that records every call, so the test checks the calls after the act step
Mockzcl_atk=>mock( )ZIF_ATK_MOCKStrict double: every call is declared up front, any other call fails at once

Every double follows the same shape: ZCL_ATK is the only entry point and takes the name of a global interface. The instance methods of the interface and of its component interfaces can be configured; static methods cannot be doubled. Classes are rejected: from ABAP Cloud code, the calls of a class double cannot be taken over by method name, so extract an interface and let the code depend on it, or double the class with CL_ABAP_TESTDOUBLE directly. instance( ) hands out the object that is injected into the code under test. Rules and checks are fluent interfaces, method names, parameter names and values are checked against the doubled type while the test is set up, and errors surface through one exception class, ZCX_ATK.

Cheat sheet

I want to...Write
inject a doubleNEW zcl_service( CAST #( double->instance( ) ) )
answer a methodstub->when( 'GET_ORDER' )->returns( order )
answer only for an argumentstub->when( 'GET_ORDER' )->with( parameter = 'ORDER_ID' value = '4711' )->returns( order )
fill an EXPORTING or CHANGING parameterstub->when( 'READ' )->sets( parameter = 'MESSAGES' value = messages )
raise an exceptionstub->when( 'GET_ORDER' )->raises( NEW zcx_not_found( ) )
check that a method was calledspy->was_called( 'WRITE' )
check the arguments of the callspy->was_called( 'WRITE' )->with( parameter = 'ACTION' value = 'CANCELLED' )
check how often it was calledspy->was_called( 'WRITE' )->with( parameter = 'ACTION' value = 'CANCELLED' )->times( 2 )
check that a method was not calledspy->was_not_called( 'DELETE' )
declare every call up frontmock->expect_call( 'WRITE' )->times( 2 ) ... mock->verify( )
name a method of a component interfacewhen( 'WRITE' ) or when( 'ZIF_LOG~WRITE' )

Names are not case sensitive. Values are converted to the parameter type without loss, so '4711' fits a numeric text of length 10, and '47A1' is rejected.

Dummy

The code under test needs the collaborator, for example as a constructor parameter, but the scenario never calls it. Any call fails the test and names the method that was called.

InterfacePurpose
ZIF_ATK_DUMMYinstance( )
DATA(unused_log) = zcl_atk=>dummy( 'ZIF_AUDIT_LOG' ).

cut = NEW zcl_order_service( repository = CAST #( repository->instance( ) )
                             audit_log  = CAST #( unused_log->instance( ) ) ).

Stub

Answers the questions of the code under test. when( ) starts a rule for one method, with( ) narrows it to an argument value, and returns( ), sets( ) or raises( ) define the answer; sets( ) covers EXPORTING and CHANGING parameters alike. Parameters without with( ) match any value. When several rules match a call, the one with the most with( ) conditions wins, on a tie the one written last. Once a method has rules, a call that matches none of them fails the test; a method without rules returns initial values. Values are converted to the type of the parameter without loss: '4711' for a numeric text becomes 0000004711, while '47A1', a text longer than the parameter or decimals that would be rounded are rejected. raises( ) accepts only exceptions the method declares, or CX_NO_CHECK ones.

InterfacePurpose
ZIF_ATK_STUBinstance( ), when( )
ZIF_ATK_CALL_RULEFluent: with( ), returns( ), sets( ), raises( )
DATA(repository) = zcl_atk=>stub( 'ZIF_ORDER_REPOSITORY' ).

repository->when( 'GET_ORDER' )->with( parameter = 'ORDER_ID' value = '4711' )->returns( open_order ).
repository->when( 'GET_ORDER' )->returns( unknown_order ).

repository->when( 'READ_TOTALS' )->sets( parameter = 'NET' value = 100 )->sets( parameter = 'TAX' value = 24 ).

repository->when( 'GET_ORDER' )->with( parameter = 'ORDER_ID' value = '0000' )->raises( NEW zcx_order_not_found( ) ).

Spy

A stub that also records every call. The test checks the calls after the act step, and every step of the check is a check of its own: was_called( ) fails the test unless the method was called at least once, each with( ) fails unless a call had the arguments named so far, and times( n ) fails unless exactly n calls matched. So a check that stops early still checks something, and was_called( 'WRITE' ) alone reads exactly as it works. For a method that must not be called, use was_not_called( ); times( 0 ) is rejected. A failed check shows the expected arguments, the closest actual call with the method and line it came from, and the parameters that differ.

InterfacePurpose
ZIF_ATK_SPYinstance( ), when( ), was_called( ), was_not_called( )
ZIF_ATK_CALL_VERIFICATIONFluent: with( ), times( ) - each one checks at once
DATA(audit_log) = zcl_atk=>spy( 'ZIF_AUDIT_LOG' ).

cut->cancel( '4711' ).

audit_log->was_called( 'WRITE'
  )->with( parameter = 'ORDER_ID' value = '4711'
  )->with( parameter = 'ACTION' value = `CANCELLED`
  )->times( 1 ).

audit_log->was_not_called( 'DELETE' ).
WRITE was called, but never with the expected arguments.
Check the code under test, or adjust the with( ) conditions.
Expected arguments: ORDER_ID = '0000004711', ACTION = 'CANCELLED'.
Closest actual call: ORDER_ID = '0000004711', ACTION = 'CANCELED' from ZCL_ORDER_SERVICE=>ZIF_ORDER_SERVICE~CANCEL, line 6 of the method.
Differs in: ACTION.

Mock

A strict double for scenarios where any unplanned call is a bug. Every expected call is declared with expect_call( ) before the act step; a call that was not declared fails the test at once, and verify( ) reports declared calls that did not happen as often as declared. Without times( ) an expectation expects exactly one call, and it can answer like a stub rule. Two expectations for the same method are two calls: each call goes to an expectation that still waits for a call, and among those to the most specific one.

InterfacePurpose
ZIF_ATK_MOCKinstance( ), expect_call( ), verify( )
ZIF_ATK_CALL_EXPECTATIONFluent: with( ), times( ), returns( ), sets( ), raises( )
DATA(audit_log) = zcl_atk=>mock( 'ZIF_AUDIT_LOG' ).
audit_log->expect_call( 'WRITE' )->with( parameter = 'ACTION' value = `CANCELLED` ).

cut->cancel( '4711' ).

audit_log->verify( ).

Error messages

Every mistake in a test surfaces through ZCX_ATK. Its text has the same three parts every time, in this order: what went wrong, how to fix it, and the facts - the arguments of the call and the method and line of the code under test that made it, the rules that exist, the closest one and where it differs, or what a value would become. In the ABAP Unit result, the first part is the message of the failure; the fix and the facts are its detail, shown under it (Analysis in the SAP GUI, Details in ADT). The examples below show the parts on separate lines. Close misspellings of method and parameter names get a suggestion; names that are far from every candidate get the list of candidates with their kinds. The texts live in message class ZATK; exceptions of the test double framework never reach the test untranslated. Failures during the act step are recorded without stopping the code under test, so a CATCH cx_root in the code under test cannot hide them; the facts of such a failure name the method and the line that called the double (read from the XCO call stack, with the line counted from the METHOD statement).

ZCL_ORDER_SERVICE is a class, and ATK doubles interfaces only.
Extract an interface from ZCL_ORDER_SERVICE, or double it with CL_ABAP_TESTDOUBLE.
Method GET_ORDER has no parameter CUSTOMER.
Pick one of the available names.
Available: ORDER_ID (IMPORTING), RESULT (RETURNING).
Value '12.345' does not fit parameter AMOUNT of type TY_AMOUNT (P LENGTH 8 DECIMALS 2).
Pass a value of that type, for example a typed variable.
Instead, the parameter would hold: 12.35.
FIRST_NAME of SPLIT_NAME is declared EXPORTING, not as an input.
with( ) is for IMPORTING and CHANGING; try sets( ) or returns( ).

Before and After

Package ZATK_DEMO holds a small order service, ZCL_ATK_DEMO_ORDER_SERVICE: it reads an order from a repository and, when it cancels an open order, writes the cancellation to an audit log. Both collaborators are injected through the constructor. Its test include tests the same scenarios twice, in ltc_with_raw_atdf with the classic ABAP Test Double Framework and in ltc_with_atk with the library; the examples below are taken from there.

" Before
repository = CAST zif_atk_demo_order_repo( cl_abap_testdouble=>create( 'ZIF_ATK_DEMO_ORDER_REPO' ) ).
audit_log  = CAST zif_atk_demo_audit_log( cl_abap_testdouble=>create( 'ZIF_ATK_DEMO_AUDIT_LOG' ) ).
cut = NEW zcl_atk_demo_order_service( repository = repository
                                      audit_log  = audit_log ).
" After
repository = zcl_atk=>stub( 'ZIF_ATK_DEMO_ORDER_REPO' ).
audit_log  = zcl_atk=>spy( 'ZIF_ATK_DEMO_AUDIT_LOG' ).
cut = NEW zcl_atk_demo_order_service( repository = CAST #( repository->instance( ) )
                                      audit_log  = CAST #( audit_log->instance( ) ) ).

Return a value for a specific argument

" Before
cl_abap_testdouble=>configure_call( repository )->returning( lth_orders=>cancelled_order( ) ).
repository->get_order( '4711' ).

DATA(is_cancelled) = cut->is_cancelled( '4711' ).

cl_abap_unit_assert=>assert_true( act = is_cancelled msg = `A cancelled order must be reported as cancelled` ).
" After
repository->when( 'GET_ORDER' )->with( parameter = 'ORDER_ID' value = '4711' )->returns( lth_orders=>cancelled_order( ) ).

DATA(is_cancelled) = cut->is_cancelled( '4711' ).

cl_abap_unit_assert=>assert_true( act = is_cancelled msg = `A cancelled order must be reported as cancelled` ).

The configuration is one statement, and the second line of the classic version - a call that only records its arguments - is gone.

Raise an exception

" Before
cl_abap_testdouble=>configure_call( repository )->raise_exception( NEW zcx_atk_demo_not_found( ) ).
repository->get_order( '4711' ).
" After
repository->when( 'GET_ORDER' )->raises( NEW zcx_atk_demo_not_found( ) ).

The recording call of the classic version declares RAISING zcx_atk_demo_not_found, so the test method needs a RAISING clause for a line that never raises. The library also checks that GET_ORDER declares the exception.

Check that a method was called once, with the right arguments

" Before
cl_abap_testdouble=>configure_call( repository )->returning( lth_orders=>open_order( ) ).
repository->get_order( '4711' ).
cl_abap_testdouble=>configure_call( audit_log )->and_expect( )->is_called_times( 1 ).
audit_log->write( order_id = '4711'
                  action   = zif_atk_demo_audit_log=>action-cancelled ).

cut->cancel( '4711' ).

cl_abap_testdouble=>verify_expectations( audit_log ).
" After
repository->when( 'GET_ORDER' )->returns( lth_orders=>open_order( ) ).

cut->cancel( '4711' ).

audit_log->was_called( 'WRITE'
  )->with( parameter = 'ORDER_ID' value = '4711'
  )->with( parameter = 'ACTION' value = zif_atk_demo_audit_log=>action-cancelled
  )->times( 1 ).

Five statements become two, and the check is written after the act step instead of before it. When it fails, the message shows the closest actual call (see Spy).

Check that a method was not called

" Before
cl_abap_testdouble=>configure_call( repository )->returning( lth_orders=>cancelled_order( ) ).
repository->get_order( '4711' ).
cl_abap_testdouble=>configure_call( audit_log )->ignore_all_parameters( )->and_expect( )->is_never_called( ).
audit_log->write( order_id = '4711'
                  action   = `` ).

cut->cancel( '4711' ).

cl_abap_testdouble=>verify_expectations( audit_log ).
" After
repository->when( 'GET_ORDER' )->returns( lth_orders=>cancelled_order( ) ).

cut->cancel( '4711' ).

audit_log->was_not_called( 'WRITE' ).

No placeholder arguments for a call that must not happen, and no ignore_all_parameters( ) or verify_expectations( ).

Every call declared up front

The classic framework has no strict mode: a call without a matching configuration returns initial values. With the library, a mock fails the test at the undeclared call:

" After
DATA(strict_log) = zcl_atk=>mock( 'ZIF_ATK_DEMO_AUDIT_LOG' ).
strict_log->expect_call( 'WRITE' )->with( parameter = 'ACTION' value = zif_atk_demo_audit_log=>action-cancelled ).

service->cancel( '4711' ).

strict_log->verify( ).

The same mistake, both messages

Three mistakes, each made twice in the demo test class - once with CL_ABAP_TESTDOUBLE, once with the library - and what ABAP Unit shows for each. The texts are copied from the ADT result on an S/4HANA 2023 system; the first line is the message of the failure, the rest its details.

The expected action is misspelled

The test expects CANCELED; the service writes CANCELLED.

" Before
cl_abap_testdouble=>configure_call( audit_log )->and_expect( )->is_called_times( 1 ).
audit_log->write( order_id = '4711' action = `CANCELED` ).
cut->cancel( '4711' ).
cl_abap_testdouble=>verify_expectations( audit_log ).
Exception Error <CX_ATD_EXCEPTION>
[ ABAP Testdouble Framework ] Method WRITE was expected to be called 1 times, but was called 0 times
" After
cut->cancel( '4711' ).
audit_log->was_called( 'WRITE' )->with( parameter = 'ACTION' value = `CANCELED` ).
WRITE was called, but never with the expected arguments.
Check the code under test, or adjust the with( ) conditions.
Expected arguments: ACTION = 'CANCELED'.
Closest actual call: ORDER_ID = '0000004711', ACTION = 'CANCELLED' from ZCL_ATK_DEMO_ORDER_SERVICE=>ZIF_ATK_DEMO_ORDER_SERVICE~CANCEL, line 6 of the method.
Differs in: ACTION.

The classic framework counts calls that match the recorded one: zero, and nothing about the call that did happen. The library shows the call, the parameter that differs and the line of the code under test that made it.

The double is configured for another order

The double answers for order 0815; the code under test asks for 4711.

" Before
cl_abap_testdouble=>configure_call( repository )->returning( lth_orders=>cancelled_order( ) ).
repository->get_order( '0815' ).
DATA(is_cancelled) = cut->is_cancelled( '4711' ).
cl_abap_unit_assert=>assert_true( act = is_cancelled msg = `A cancelled order must be reported as cancelled` ).
Critical Assertion Error: 'A cancelled order must be reported as cancelled'
True expected
" After
repository->when( 'GET_ORDER' )->with( parameter = 'ORDER_ID' value = '0815' )->returns( lth_orders=>cancelled_order( ) ).
DATA(is_cancelled) = cut->is_cancelled( '4711' ).
GET_ORDER was called with arguments that match none of its rules.
Add a rule for these arguments, or check the code under test.
Actual arguments: ORDER_ID = '0000004711'.
Called from: ZCL_ATK_DEMO_ORDER_SERVICE=>ZIF_ATK_DEMO_ORDER_SERVICE~IS_CANCELLED, line 2 of the method.
Rules: (ORDER_ID = '0000000815').
Closest rule differs in: ORDER_ID.

The classic framework finds no matching configuration, returns an initial order and says nothing; the test fails at its own assertion, and the developer starts debugging. The library fails at the call, with both order numbers side by side.

A value that does not fit the parameter

'ABC' for an order number of type N LENGTH 10.

" Before
DATA(order_id) = 'ABC'.
cl_abap_testdouble=>configure_call( repository )->returning( lth_orders=>cancelled_order( ) ).
repository->get_order( order_id ).
Critical Assertion Error: 'A cancelled order must be reported as cancelled'
True expected
" After
repository->when( 'GET_ORDER' )->with( parameter = 'ORDER_ID' value = order_id )->returns( lth_orders=>cancelled_order( ) ).
Exception Error <ZCX_ATK>
Value 'ABC' does not fit parameter ORDER_ID of type TY_ORDER_ID (N LENGTH 10).
Pass a value of that type, for example a typed variable.
Instead, the parameter would hold: '0000000000'.

The recording call converts 'ABC' to 0000000000 silently, so the classic framework is configured for an order that no test asks for; the failure comes from the assertion at the end. The library rejects the value where it is written.

Summary

In the four scenarios that exist in both test classes, the doubles need 6 statements instead of 14, and none of them is a call that only records arguments. What the library takes off your hands, compared with the classic framework used directly:

With CL_ABAP_TESTDOUBLE directlyWith the library
Two statements per configuration: configure_call( ), then a call that looks like a real call but only records argumentsOne statement: when( )->with( )->returns( )
The recording call needs every mandatory parameter, even those the scenario does not care about, and a RAISING clause when the method raisesOnly the parameters named in with( ) matter; the rest match any value
A never-called check needs ignore_all_parameters( ), a placeholder call and verify_expectations( )was_not_called( )
Expectations are written before the act step and checked by verify_expectations( )Spy checks are written after the act step, where the reader expects them
set_parameter( ) for outputs; EXPORTING and CHANGING are not distinguished for the readersets( ) for both
A call without a matching configuration returns initial values silentlyA call that matches no rule of a configured method fails the test at the call, with the arguments
A wrong value in a configuration fails the test somewhere later, or not at allValues are checked against the parameter type when the rule is written; the message says what the parameter would hold instead
A failed expectation says how many calls were made, not whichThe failure shows the expected arguments, the closest call and the parameter that differs
Which line of the code under test made the unwanted or unmatched call is in the stack trace, if the failure happens at the call at allEvery recorded call carries the method and line of the code under test that made it: from ZCL_ORDER_SERVICE=>ZIF_ORDER_SERVICE~CANCEL, line 6 of the method
Framework exceptions (CX_ATD_EXCEPTION) with technical textsOne exception class whose text says what went wrong and how to fix it, with a suggestion for misspelled names

One difference to keep in mind: method and parameter names are strings. The classic framework catches a misspelled name at activation, the library the first time the test runs, with a suggestion for the closest name; a rename refactoring in ADT does not update them.

Limitations

  • Only global interfaces can be doubled. Classes are rejected with a message that says why (see Available Test Doubles); local interfaces of a test include cannot be described by name.
  • Static methods and events of the doubled interface are not doubled. when( ) on a static method reports it as unknown.
  • An optional parameter the caller leaves out is recorded as initial, not with its DEFAULT value, because RTTI does not expose defaults. A condition on such a parameter is not met, and the failure marks the parameter with (not supplied).
  • Mandatory parameters typed generically as SORTED TABLE or HASHED TABLE cannot be doubled yet; ANY TABLE, INDEX TABLE, STANDARD TABLE, ANY, DATA, c, n, p, x and the other generic types work. A condition on a generic parameter keeps the type of the value the test gives; the value of the call is compared in that type, or the other way round.
  • A value given with sets( ) or returns( ) for a generically typed output (EXPORTING or CHANGING TYPE any, TYPE STANDARD TABLE, ...) is handed to the framework without a check against the variable of the caller, because that variable is not known when the rule is written.
  • with( ) compares for equality. Matchers such as "any text containing" are planned.
  • A mock that is never verified checks only the undeclared calls; a forgotten verify( ) is planned to be detected by the testability analyzer.
  • raises( ) accepts exceptions the method declares and CX_NO_CHECK ones; an undeclared CX_DYNAMIC_CHECK exception is rejected, although ABAP would allow it.

Design Goals-Features

  • ABAP Cloud / Clean Core compatibility — passes the ATC variant ABAP_CLOUD_DEVELOPMENT_DEFAULT
  • Released APIs only (release contract C1), no dependencies besides SAP's own classes
  • Built on CL_ABAP_TESTDOUBLE — no own mocking engine; both can be used in the same test class
  • Tests in arrange-act-assert order: spies are checked after the act step
  • Messages in three parts - what went wrong, how to fix it, the facts; framework exceptions never reach the test untranslated
  • Method names, parameter names and values checked against the doubled type with RTTI
  • Small public surface: one facade class, fluent interfaces and one exception class; everything else is local to ZCL_ATK
  • Test code only — ZCL_ATK is FOR TESTING, so production code cannot depend on it
  • Clean Code following the Clean ABAP Style Guides
  • Modern ABAP syntax (7.58 / 9.14) — expressions, inline declarations, string templates
  • 177 unit tests of the library run with ABAP Unit against the real CL_ABAP_TESTDOUBLE, and off-stack on every push with the abaplint transpiler; abaplint on every push
  • Documented with ABAP Doc on every public declaration

To-Do

Work planned for the next releases, in the order it will be built. What is already on main but not yet released is listed under Unreleased in CHANGELOG.md.

Next phases

  1. Assertions — zcl_atk=>expect( actual )->to_equal( expected ) on top of CL_ABAP_UNIT_ASSERT, with matchers for tables, structures and exceptions, and failure messages that always show the expected and the actual value
  2. Testability analyzer — an ATC check or abaplint rules that explain why a class is hard to test (SELECT in a method, NEW of dependencies, static calls, function modules) and how to fix each finding; it will also check the method and parameter names in tests that use the library, and find mocks without verify( )
  3. Koans — red-to-green exercises built on the test doubles and the assertions

Improvements to the test doubles

  • Consecutive answers — a different answer for the first, second and following calls of a method
  • Matchers for with( ) — conditions such as "any text containing" instead of equality
  • Argument capture — read the arguments of a recorded call in the test, for assertions on structures and tables with the developer's own checks
  • Events — raise the events of a doubled type
  • Generic sorted and hashed tables — mandatory parameters typed SORTED TABLE or HASHED TABLE
MIT, read

History

Latest

v1.1.1

Changed 3, Fixes 1

Notes

No change in behavior or public API; updating from 1.1.0 is a plain pull.

Changed
  • README: section The same mistake, both messages - three mistakes made with CL_ABAP_TESTDOUBLE and with the library, with the texts ABAP Unit shows for each.
  • Off-stack toolchain: the unit tests and abaplint use CL_ABAP_TESTDOUBLE and ABAP Unit of open-abap-core; the ATDF stand-in, the runtime patches and abaplint-stubs/ are gone, and no test is skipped off-stack any more. Needs @abaplint/transpiler-cli and @abaplint/runtime 2.14.3 or later. open-abap-core and open-abap-xco are development dependencies in package.json, pinned to a commit.
  • Internal: reading an argument from the test double framework and copying it into the parameter type are separate steps in lcl_doubled_method.
Fixed
  • CONTRIBUTING and the comments of the unit tests no longer say that some tests run on a system only: all 177 run off-stack as well. The release steps name the two version entries of package-lock.json.
  1. v1.1.0

    Features 1, Changed 5, Fixes 10

    Notes
    Added
    • Every recorded call carries the method of the code under test that made it and the line within that method, read from the XCO call stack while the double answers (from ZCL_ORDER_SERVICE=>ZIF_ORDER_SERVICE~CANCEL, line 6 of the method). A failure at the call - an unmatched or unwanted call, a dummy that is called - has it as the fact Called from:; the matching and closest calls of a spy or mock failure show it after their arguments.
    Changed
    • Breaking: every step of a spy check checks on its own. was_called( ) fails the test at once unless the method was called at least once, each with( ) fails unless a recorded call has the arguments named so far, and times( n ) needs n of 1 or more - times( 0 ) is rejected, was_not_called( ) is the check for a method that must not be called. Before, a check without times( ) checked nothing. New problems no_call_recorded (027/124) and no_matching_call (028/125); negative_expected_calls (016/113) is gone, times( ) of a spy and of a mock both raise invalid_expected_calls.
    • A failure reported to ABAP Unit has what went wrong as its message and the fix and the facts as its detail, so the failure list stays readable and the rest shows under it (Analysis in the SAP GUI, Details in ADT). zcx_atk->get_text( ) is unchanged.
    • The facts about a value that does not fit name the type of the value and what the parameter would hold instead as labeled sentences (The value has the type:, Instead, the parameter would hold:), so long type names are no longer cut at the 50 characters of a message placeholder.
    • The failure of was_called( ) on a method that was never called names the other methods the double received calls for, with their counts, or says that the double received no call at all. The arguments of a call of a method without inputs read none, not any.
    • 168 unit tests.
    Fixed
    • A with( ) condition on an optional parameter the caller left out is never met, not even with an initial value: the method may have seen its DEFAULT, which the double cannot know.
    • A call goes to an expectation that still waits for a call before a more specific one that got all its calls, as documented; the failure of an unmet expectation lists the calls that expectation answered, not the calls a twin expectation took.
    • Parameters typed with the generic c, n, p or x accept values of any length; before, CREATE DATA silently gave them the standard length and every longer value was refused.
    • A table value in another row order than the sorted table type of the parameter is accepted.
    • A condition on a generic parameter also matches when the value of the call fits the type of the condition the other way round ('4711' against a numeric text).
    • A method named with the prefix of the doubled interface itself ('ZIF_ORDERS~GET_ORDER'), or by an alias declared in the interface, is found.
    • A failure to read the call stack no longer fails the call; the origin is left out.
    • An enumerated value is only compared with a value of its own type.
    • Fix texts for with( ), sets( ) and returns( ) name the right alternative, and an interface without instance methods lists none instead of nothing.
    • The unit tests of ZCL_ATK no longer depend on the demo package.
  2. v1.0.0

    Features 9

    Notes
    Added

    Test doubles on top of CL_ABAP_TESTDOUBLE.

    • ZCL_ATK with dummy( ), stub( ), spy( ) and mock( ) for global interfaces. Classes are rejected with a message that points to an interface or to CL_ABAP_TESTDOUBLE.
    • Rules with when( ), with( ), returns( ), sets( ) and raises( ); the most specific rule wins, and a call that matches no rule of a method fails the test.
    • Spy checks with was_called( )->with( )->times( ) and was_not_called( ), after the act step.
    • Mock expectations with expect_call( )->times( ) and verify( ). Two expectations for the same method are two calls: a call goes to the most specific expectation that still waits for a call.
    • Methods of component interfaces (INTERFACES inside the doubled interface), named with or without their interface prefix.
    • Method names, parameter names and values checked with RTTI, with suggestions for misspelled names; values converted without loss.
    • ZCX_ATK and message class ZATK: every text has three parts, in this order - what went wrong, how to fix it, and the facts: the actual arguments, the rules or expectations of the method and where the closest one differs, the matching or closest calls, what a value that does not fit would become, the parameters or outputs a method has, the exceptions it declares. An optional parameter the caller left out is shown as (not supplied).
    • 133 unit tests against the real CL_ABAP_TESTDOUBLE, and package ZATK_DEMO with the same scenarios tested with the classic framework and with the library.
    • abaplint in ABAP Cloud mode and the unit tests off-stack (abaplint transpiler, open-abap-core) on every push.
Unit testing
  1. ABAP Test Double FrameworkPlatform test doubles through CL_ABAP_TESTDOUBLEfrom ABAP
  2. abap2UI5 headless frontendDrives abap2UI5 app classes from ABAP Unit testsfrom ABAP
  3. @abap2ui5/mcp-serverMCP server for the abap2UI5 check, deploy and run loopfrom ABAP
  4. assertFluent assertion helper for ABAP Unit testsfrom ABAP
  5. @abaplint/transpilerTranspiles ABAP to JavaScript to run on Node.jsfrom ABAP
  6. Modernization Agents for SAP on AWSAWS agents for ABAP, PI/PO and BW modernisation workfrom ABAP
  7. abap_db_preparatorReplays stored database snapshots in ABAP unit testsfrom ABAP
  8. cacamber-BDD-for-ABAPBDD framework turning Given/When/Then into ABAP Unit stepsfrom ABAP
  9. abap-utesthelpHelper classes for ABAP Unit tests and test doublesfrom ABAP
  10. ABAP Debugger Data View ExtensionDebugger menu entry rendering a table or structure as VALUE codefrom ABAP
  11. regression_testSnapshot testing library for ABAP, from debugger or unit testfrom ABAP
  12. abapCovUploads ABAP Unit code coverage to codecov.iofrom ABAP
  13. ABAP remote filesystemVS Code extension editing ABAP systems over ADT as a filesystemfrom ABAP
  14. open-steamgateLocal ABAP server serving /IWBEP/ OData v2 without an SAP systemfrom ABAP
  15. express-icf-shimShim running an ABAP ICF handler behind an Express 4.x serverfrom ABAP
  16. open-abap-coreCore ABAP artifacts for code transpiled by @abaplint/transpilerfrom ABAP
  17. ABAP Development Tools for VS CodeSAP's ABAP tools in VS Code, over RFC to on-premise or HTTP to cloudfrom ABAP
  18. mockup_loaderLoader of Excel-prepared ABAP unit test data, with stub generationfrom ABAP
  19. mockAA free mocking framework for ABAP, published under the Apache License 2.0.from ABAP
  20. Vise_for_ABAPA characterisation-test helper: `ZCL_VISE=>GRIP` records a field's value once and fails a later run if it has changed.from ABAP
  21. abap-senchaBDD/TDD assertion library for ABAP Unit, styled after Chai.jsfrom ABAP
  22. abap-test-containerGitHub-hosted test data container for ABAP Cloudfrom ABAP
  23. abap-unit-runnerApplication job that runs and schedules ABAP Unit tests in the cloudfrom ABAP
  24. adash-servicesBackend exposing ABAP Unit runs and coverage over REST and ODatafrom ABAP

Details

Sources3
GitHub APIdiscovered herechecked
github.com/greltel/abap-test-kittree, pushes, contributors, CIchecked
README.mdquoted, and what the entry declareschecked

Sources are graph entities: the same page may back several entries, and an unreachable source flags every row that rests on it.

Compatibility and provenance
discovered viaGitHub API
first seen in the atlas
ecosystem and domainabap, developer-experience
abaplint
issues under its own abaplint configuration0
lines6,707
statements3,993
objects22
files43
analysed, main
abaplint version2.120.92
Flavors1
ABAP Cloud
“The library is written against the **ABAP for Cloud Development** language version, follows **Clean Core** principles, and consumes **released APIs only**.” — greltel/abap-test-kit — READMEchecked
History in the atlas1
  1. First seen.
For your agent
Ref
artifact:greltel-abap-test-kit
JSON
/artifacts/greltel-abap-test-kit.json
MCP
Connecting an agent