Address Nikola's batching and UI-handler feedback on PR #132. Preserve existing false-positive guards and defer the unconfirmed IsHandled policy. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: f841a18b-535a-498e-96f2-f279b5378da5
4.5 KiB
| bc-version | domain | keywords | technologies | countries | application-area | ||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
testing |
|
|
|
|
Wire UI handlers and verify meaningful outcomes
Description
A test runs headless, so every UI call on the executed path must be intercepted by a matching handler named in [HandlerFunctions(...)]. The list is a two-sided contract: an unhandled UI call aborts the test, while Microsoft documents that every nonoptional listed handler must execute at least once or the test fails.
Optionality is declared, not inferred. SendNotificationHandler and RecallNotificationHandler accept a HandlerIsOptional argument, so [SendNotificationHandler(true)] may stay listed on a run that never raises the notification, while the same attribute written without that argument is nonoptional like every other handler type. Notifications are conditional by nature, so an optional notification handler is listed precisely because the scenario may or may not reach it.
Beyond that wiring guarantee, the test must verify the behavior it cares about. The appropriate pattern depends on the contract: a handler can capture concrete page state or a result and the test can assert that semantic postcondition after RunModal; assertions inside a handler are also supported. Queue/enqueue/dequeue and LibraryVariableStorage.AssertEmpty are useful when interaction order, count, text, replies, or a scripted sequence is itself part of the contract, but they are not mandatory for every handler.
Best Practice
List the handlers the scenario triggers and keep an optional notification handler listed for a notification the scenario may conditionally raise. Make the test and its handlers prove the scenario's contract. For a single modal page, reset a capture variable before the action, capture a concrete value from the page in the handler, and assert the expected value after RunModal. This proves the captured result, not an exact call count or sequence: repeated calls can overwrite earlier captures.
When interaction count, text, order, or replies are part of the contract, drive the handlers with the Library - Variable Storage codeunit. Clear LibraryVariableStorage during test initialization, then enqueue expected text and replies in interaction order. A shared ConfirmHandler dequeues the expected question, verifies it with Assert.ExpectedConfirm, and dequeues the Boolean reply to return; a message handler can use Assert.ExpectedMessage with the next expected text. These asserts match a stable text fragment. Finish with LibraryVariableStorage.AssertEmpty to catch unconsumed expectations; each handler must also dequeue and verify its interaction so unexpected calls cannot pass silently. Equivalent explicit assertions are valid; absence of this library alone is not a finding.
Prefer one reusable handler of each type within a test codeunit where practical, with individual tests supplying their expectations and replies. This is a maintainability recommendation, not a platform requirement; specialized handlers remain valid when their contracts differ. The paired confirmation-and-message samples run the same flow and check its Boolean result, but only the good sample verifies the expected interactions. The modal capture and optional notification scenarios illustrate contracts that do not require a scripted queue.
See sample: ui-handlers-in-tests.good.al.
Anti Pattern
Omitting a handler for a UI call, listing a nonoptional handler the path never reaches, or claiming action success from a Boolean set before the action runs. A handler that only closes a page can also leave the test without a semantic assertion. Blindly confirming or dismissing messages does not verify a contract that requires specific interactions. Do not flag the absence of queue storage by itself; require interaction verification only when the test needs to prove order, count, text, replies, or a scripted sequence. Do not flag a listed [SendNotificationHandler(true)] or [RecallNotificationHandler(true)] that the run does not reach, and never propose removing one: the entry is what keeps the test passing on the runs where the notification does fire.
See sample: ui-handlers-in-tests.bad.al.