131005 “Library – Dialog Handler” – build test handlers differently

by Dec 3, 2022AL Language

Home 9 Development 9 AL Language 9 131005 “Library – Dialog Handler” – build test handlers differently

Test UI Handlers are necessary in many cases if you want to test any logic that shows confirm, message, or different page… The most common are ConfirmHandler (to handle Confirm()) and MessageHandler (to handle Message()). To find out more about Handlers, check Microsoft Learn.

This article will take a different approach to creating handlers than what’s published at Microsoft Learn – using W1 Library “Library – Dialog Handler”.

Let’s start with a basic example of a test with a standard confirm handler (a real test from one of my projects).

[Test]
[HandlerFunctions('NotReservedReleaseForWhseConfirmHandler')]
procedure TestSomething()
var
    SalesHeader: Record "Sales Header";
    SalesLine: Record "Sales Line";
begin
    // [GIVEN] Sales Retail Order exists with not reserved line
    PTELibrarySales.CreateSalesRetailOrder(SalesHeader);
    LibrarySales.CreateSalesLineWithUnitPrice(SalesLine, SalesHeader, '', 5, 2);
    SalesLine.SetRecFilter();

    // [WHEN] The order is manually released to warehouse 
    PTELibrarySales.ReleaseRetailSalesOrderToWhse(SalesHeader);

    // [THEN] The confirm is shown, released for warehouse status is set to Released and not reserved line is removed
    // NotReservedReleaseForWhseConfirmHandler handler
    LibraryAssert.AreEqual(SalesHeader."TKA Released for Ret. Whse."::Released, SalesHeader."TKA Released for Ret. Whse.", SalesHeader.FieldCaption("TKA Released for Ret. Whse."));
    LibraryAssert.RecordIsEmpty(SalesLine);
end;

[ConfirmHandler]
procedure NotReservedReleaseForWhseConfirmHandler(Question: Text[1024]; var Reply: Boolean);
var
    NotReservedReleaseForWhseQst: Label 'Some of lines are not fully reserved. Do you want to release the order for warehouse?';
begin
    LibraryAssert.ExpectedMessage(NotReservedReleaseForWhseQst, Question);
    Reply := true;
end;

There is nothing wrong with this approach. However, there are some limitations/problems that I want to mitigate. Firstly, if you want to check the order of how the handlers are called, the code required (with global integer/boolean/… variables) is strange… Also, if your test should cause more than one Confirm/Message/StrMenu, it is a bit problematic (again, you need some global variables) to check whether all dialogs were shown. Lastly, I prefer to have the full test info within one procedure.

So what’s my solution? Codeunit 131005 “Library – Dialog Handler”

I found a test library codeunit 131005 “Library – Dialog Handler”, which allows you to build better handlers (at least, better for me). It is not used anywhere in W1 tests (?), but it’s used in some localisation tests (Czech tests).

codeunit 131005 "Library - Dialog Handler"
{
    var
        Assert: Codeunit Assert;
        LibraryVariableStorage: Codeunit "Library - Variable Storage";

    procedure HandleMessage(Message: Text)
    begin
        Assert.ExpectedMessage(LibraryVariableStorage.DequeueText(), Message);
    end;

    procedure HandleConfirm(Question: Text; var Reply: Boolean)
    begin
        Assert.ExpectedConfirm(LibraryVariableStorage.DequeueText(), Question);
        Reply := LibraryVariableStorage.DequeueBoolean();
    end;

    procedure HandleStrMenu(Options: Text; var Choice: Integer; Instruction: Text)
    begin
        Assert.ExpectedStrMenu(
            LibraryVariableStorage.DequeueText(), LibraryVariableStorage.DequeueText(),
            Instruction, Options);
        Choice := LibraryVariableStorage.DequeueInteger();
    end;

    procedure SetExpectedMessage(Message: Text)
    begin
        LibraryVariableStorage.Enqueue(Message);
    end;

    procedure SetExpectedConfirm(Question: Text; Reply: Boolean)
    begin
        LibraryVariableStorage.Enqueue(Question);
        LibraryVariableStorage.Enqueue(Reply);
    end;

    procedure SetExpectedStrMenu(Options: Text; Choice: Integer; Instruction: Text)
    begin
        LibraryVariableStorage.Enqueue(Instruction);
        LibraryVariableStorage.Enqueue(Options);
        LibraryVariableStorage.Enqueue(Choice);
    end;

    procedure ClearVariableStorage()
    begin
        LibraryVariableStorage.Clear();
    end;
}

As you can see from the code above, it is not only for Confirms but also for Messages and StrMenus. The codeunit is pretty simple – it only stores all expected handlers using “Library – Variable Storage” to queue (= FIFO processing) and retrieve it when it should be handled.

See refactored example below; changes are

  • Initialize() procedure called at the beginning of each test to clear existing handler definitions using ClearVariableStorage()
  • Simpler Handler – it just calls HandleConfirm() procedure from LibraryDialogHandler
    • It will always be the same (so you could add it to your custom snippets if you are using it)
  • LibraryDialogHandler.SetExpectedConfirm(NotReservedReleaseForWhseQst, true);
    • The most important thing – the definitions of confirm/message/strmenu
    • Add it just before the line that should cause the dialog = you can check the order of dialogs, and if the test should cause more than one dialog of the same type, it’s much easier to do it this way than using the standard handler procedure.
local procedure Initialize();
begin
    LibraryDialogHandler.ClearVariableStorage();
end;

[Test]
[HandlerFunctions('ConfirmHandler')]
procedure TestSomething()
var
    SalesHeader: Record "Sales Header";
    SalesLine: Record "Sales Line";
    NotReservedReleaseForWhseQst: Label 'Some of lines are not fully reserved. Do you want to release the order for warehouse?';
begin
    Initialize();
    // [GIVEN] Sales Retail Order exists with not reserved line
    PTELibrarySales.CreateSalesRetailOrder(SalesHeader);
    LibrarySales.CreateSalesLineWithUnitPrice(SalesLine, SalesHeader, '', 5, 2);
    SalesLine.SetRecFilter();

    // [WHEN] The order is manually released to warehouse with confirmation
    LibraryDialogHandler.SetExpectedConfirm(NotReservedReleaseForWhseQst, true);
    PTELibrarySales.ReleaseRetailSalesOrderToWhse(SalesHeader);

    // [THEN] Released for warehouse status is set to Released and not reserved line is removed
    LibraryAssert.AreEqual(SalesHeader."TKA Released for Ret. Whse."::Released, SalesHeader."TKA Released for Ret. Whse.", SalesHeader.FieldCaption("TKA Released for Ret. Whse."));
    LibraryAssert.RecordIsEmpty(SalesLine);
end;

[ConfirmHandler]
procedure ConfirmHandler(Question: Text[1024]; var Reply: Boolean);
begin
    LibraryDialogHandler.HandleConfirm(Question, Reply);
end;

MessageHandlers and StrMenuHandlers are the same – only one line to call HandleXXX procedure from the “Library – Dialog Handler” codeunit.

[MessageHandler]
procedure MessageHandler(Message: Text[1024]);
begin
    LibraryDialogHandler.HandleMessage(Message);
end;

[StrMenuHandler]
procedure StrMenuHandler(Options: Text[1024]; var Choice: Integer; Instruction: Text[1024]);
begin
    LibraryDialogHandler.HandleStrMenu(Options, Choice, Instruction);
end;

Recent Articles from the category

Data types under the microscope: List

Data types under the microscope: List

The List data type in AL language represents an ordered collection of objects that can be accessed by their index. Unlike an Array data type, a List does not have a fixed size and does not need to have its dimension specified when it is declared. The List data type...

read more
Connect to Azure Function in BC 2022 wave 2 (v21)

Connect to Azure Function in BC 2022 wave 2 (v21)

The new version of the Microsoft Dynamics 365 Business Central brought a new system module "Azure Functions" that makes integration with Azure Functions much easier and straightforward. The typical scenario, why to use Azure Functions together with Business Central,...

read more
Collectible Errors?! Is it already in use?

Collectible Errors?! Is it already in use?

Collectible Errors?! Is it already in use? This is the second part of my new article series about Collectible Errors. Let's check out the first part here: Collectible Errors?! | Microsoft Dynamics 365 - Ing. Tomáš Kapitán (kepty.cz) or you might be interested in my...

read more
Collectible Errors?!

Collectible Errors?!

Collectible Errors?! It has been already almost a year since ErrorInfo datatype & CollectibleErrors were introduced (I already have an article about basic structure: ErrorInfo data type & Collectible Errors). This article was released for the first time in...

read more
Substituting standard reports

Substituting standard reports

Report objects cannot be extended in versions previous to the Business Central 2021 release wave 1 version when ReportExtensions object was introduced. Since then, many of changes we need to do in reports can be done without creating a new copy of object. On the other...

read more
Isolated events

Isolated events

With Business Central 2022 wave 1, a new setting for event publishers was introduced. Until this version, any error in any event subscriber caused interruption to the current running process and stopped the whole activity. In some cases (such as log-in), this is...

read more
Custom Filter Tokens

Custom Filter Tokens

As a user of the Business Central you have some constants you can use to filter or insert values. These constants contain useful values for data manipulation such as t / today for date field - return current dateq / quarter for date field filters - return range of the...

read more

Sign Up for News

Certifications

Highest certification
Microsoft Data Management and
also in D365 Business Central

Microsoft Certified: Dynamics 365 Business Central Functional Consultant Associate

See other certifications here