Email this post as a PDFDevelopment · 7 October 2026 · 3 min read

AL test patterns: handler functions, library codeunits and test isolation

The pipeline post said to write tests. This is how to write ones that run reliably: the Given-When-Then shape, building data with the Library codeunits, handling dialogs and pages with handler functions, asserting errors, test isolation so one test does not pollute the next, and the two mistakes that make a test suite flaky.

Contents
  1. The shape
  2. Building data
  3. Handler functions
  4. Isolation
  5. Testing event subscribers
  6. Permissions
  7. The two flakiness causes

A test that fails randomly is worse than no test, because the team learns to ignore red. These patterns produce tests that pass when the code is right and fail when it is not, every time.

The shape

[Test]
[HandlerFunctions('ConfirmYesHandler')]
procedure ReleasingShipmentWithoutCarrierFails()
var
    Shipment: Record "CON Courier Shipment";
    ShipmentMgt: Codeunit "CON Courier Shipment Mgt.";
begin
    // [GIVEN] a shipment with no carrier
    CreateShipment(Shipment);
    Shipment.Carrier := Shipment.Carrier::" ";
    Shipment.Modify();
    // [WHEN] releasing it
    asserterror ShipmentMgt.Release(Shipment);
    // [THEN] the carrier error is raised
    Assert.ExpectedError(CarrierMissingErr);
end;

The comments are a convention Microsoft uses and tooling recognises. One rule per test; the name says what is being proven.

Building data

Never depend on the demo company. The test library apps (Tests-TestLibraries from Microsoft's test toolkit) give you LibrarySales.CreateCustomer, LibraryInventory.CreateItem, LibrarySales.CreateSalesDocumentWithItem, LibraryERM.CreateGLAccount, LibraryUtility.GenerateRandomCode and hundreds more, each creating a valid record with random keys so tests do not collide. Wrap your own entity the same way in a "CON Library - Courier" test codeunit: CreateShipment(var Shipment) that sets everything a valid shipment needs.

Handler functions

Any dialog the code under test would show has to be answered by the test, or the test hangs. [ConfirmHandler], [MessageHandler], [PageHandler], [ModalPageHandler], [RequestPageHandler], [StrMenuHandler] procedures are declared on the test codeunit and listed in [HandlerFunctions] on each test that needs them. A test that declares a handler that is not called fails, which is deliberate: it tells you the code path changed.

[ConfirmHandler]
procedure ConfirmYesHandler(Question: Text[1024]; var Reply: Boolean)
begin
    Reply := true;
end;

Isolation

TestIsolation = Codeunit (on the test runner) rolls back the database after each test codeunit; Function after each test. Set the runner to function-level isolation and every test starts from the same state. Within a test, do not rely on anything a previous test created. Single-instance codeunits keep state across tests in the same session: clear them in an Initialize procedure called at the start of each test, which is also where you set up setup records once per run (guarded by an IsInitialized flag).

Testing event subscribers

Call the base operation that raises the event (post a document with the library) and assert the effect. For subscribers you want active only in a test, use EventSubscriberInstance = Manual and BindSubscription(TestSubscriberCodeunit) inside the test, with a handler that captures what was passed.

Permissions

Run the suite under a restricted permission set (LibraryLowerPermissions.SetO365BusFull and friends) at least once per run, so a missing permission in your shipped sets fails a test rather than a user.

The two flakiness causes

  • Dates. WorkDate versus Today versus a hard-coded date; posting-date ranges; tests that pass until month-end. Use WorkDate() consistently and set it in Initialize.
  • Order dependence. Test B passes only because test A ran first. Function isolation and random keys remove it; run the suite in a random order once to prove it.

Twenty tests like this, in the pipeline on every commit, are what let you deploy on a Tuesday afternoon without a knot in your stomach.

Email this post as a PDF
A branded PDF of this post, sent to your inbox to keep, print or share. Copy in colleagues if you like.
We only use these addresses to send this PDF. Nobody is added to a mailing list. Mike at Business Boxed receives a copy. See our privacy notice.
Email this post
Talk to us
Want this done in your Business Central?
Book a 30 minute callGet the monthly newsletter

Related posts