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;