In this 92nd episode of the Areopa webinar series, presenters Christoffer Andersen (Lead Developer and TSA at Evid) and Luc van Vugt (Test Automation Lead at 4PS) walk through a year-long journey of refactoring Business Central test code. Moderated by Henrik Helgesen, the session covers how moving from spaghetti code toward SOLID-structured codeunits dramatically reduces both test execution time and the complexity of data setup — and how a matching revision of the test plan keeps everything aligned.
The Problem with End-to-End Testing
The session opens with a familiar pain point: test suites that take too long to run and are too fragile to maintain. When all business logic lives in a single monolithic codeunit — what the presenters call “unstructured spaghetti” — every test must enter from the left side of the flow and traverse the full OnRun trigger to reach any component. That means full database interaction, complex data setup with five or six [GIVEN] steps per test, and slow execution that eventually leads teams to stop running tests altogether.

The business case used throughout the webinar is Seminar Registration Posting — a posting codeunit with three key components pulled out for demonstration: CheckMandatoryHeaderFields, LinesExist, and CheckLines. These represent the repeating logic that developers actually own and need to test.
📖 Docs: Testing the application overview — Business Central — Microsoft’s starting point for understanding the AL test framework, test codeunits, and handler methods.
Effective Data Setup
Before restructuring code, Luc covers practical techniques for making data setup faster and less brittle regardless of code structure:
- Use code, not the UI — never drive data creation through page interactions in automated tests.
- Skip cleanup — don’t clean up the database before or after every test; use isolated test data instead.
- Promote shared fixtures — if a customer record is needed in every test, create it once and reuse it rather than creating it fresh each time.
- Only set up what is needed — if the test will hit an error early, don’t set up data for steps that will never be reached.
- Avoid validate calls during setup — inserting test data with
Validatetriggers additional business logic; use direct field assignment instead.
Applied together, these techniques can roughly halve test execution time even before any code restructuring takes place.
Componentized Code Structure
The first architectural step is extracting the three identified components from the main codeunit and placing them in a dedicated SeminarValidator codeunit, each as its own procedure accepting a record as a parameter. This componentized structure allows each component to be tested in isolation, with a [GIVEN] reduced to a single seminar registration record populated only with the fields relevant to the condition being tested.
The downside: the data setup complexity and execution speed per test is roughly equal to the optimized spaghetti approach. The main gain is readability and the ability to write simpler, more focused tests per component. A consequence worth noting is that components must be marked internal rather than local to be callable from a test codeunit — and the internalsVisibleTo setting in app.json must expose the production app to the test app. This is a potential security consideration if not removed before releasing the extension.
Mapping the Test Plan to Components
Luc demonstrates how a scenario-based test plan — built in a spreadsheet before any code is written — naturally reveals the component boundaries. Grouping test scenarios by the errors they trigger shows which scenarios belong to the same component. A set of seven positive/negative tests all hitting header validation errors signals that “header validation” is a standalone component.

The revised test plan separates tests into two categories: Implementation tests that run through the full posting flow, and Unit tests that call only an individual component. With componentized code, the original 12 costly end-to-end tests can be reduced to 4 implementation tests plus 13 fast unit tests.


📖 Docs: Test codeunits and test methods — Business Central — covers the SubType property, [Test] attribute, GIVEN-WHEN-THEN structure, and handler methods for UI interaction.
SOLID Code Structure
The next step goes further by removing direct database dependencies entirely. Christoffer introduces SOLID principles as a language-agnostic framework for structuring code:
- S — Single Responsibility: each codeunit does one thing.
- O — Open/Closed: outer procedures are accessible; internal logic is locked down.
- L — Liskov Substitution: implementations can be swapped without changing callers.
- I — Interface Segregation: represent behaviour through focused interfaces.
- D — Dependency Inversion: no direct dependency on the database — depend on abstractions instead.
In practice, the three validator components are each represented by an AL interface. The IRegistrationHeader interface, for example, exposes procedures like GetStatus(), GetPostingDate(), and GetSeminarNo() instead of direct field reads from a table record.

A Factory codeunit manages which implementation of each interface is active at runtime — production code uses the real validator implementations, while tests inject spy or stub implementations. A spy simply sets a Boolean when called, confirming the component was reached. A stub returns controlled values without hitting the database.

📖 Docs: Interfaces in AL — Business Central — explains how to declare AL interfaces, implement them in codeunits using the implements keyword, and use interface variables for polymorphic calls.
The Golden Middle: Componentized SOLID
Pure SOLID has a significant drawback: removing all database interaction means losing TestField, Insert, record queries, and other built-in table operators. These must be reimplemented manually, which is significant work — and often overkill if cross-table reuse isn’t a goal.
The practical compromise presented is Componentized SOLID: keep the interface and factory pattern, but pass actual temporary records (declared with VAR parameters) instead of interface-wrapped data objects. This restores TestField and other record operations, keeps all components mockable via spies, and ensures database writes stay in temporary memory during tests.
With this approach, unit tests are identical to those written for plain componentized code — a SeminarRegistrationHeader record is created, relevant fields are populated directly (without Validate), and the component procedure is called. The one end-to-end flow test uses the factory to inject spies for all three components, confirms they are each reached, and then exits. That one test is notably faster than traditional implementation tests because the heavy component logic is bypassed entirely.
The SOLID Test Plan
The final test plan replaces implementation tests with a single flow test: a test that runs the posting codeunit end-to-end but with all validators replaced by spies. The flow test confirms the wiring — that all three components are called — without exercising their internal logic. All other scenarios become pure unit tests.


The result: 1 costly flow test and 13 fast unit tests. Component unit tests run in approximately 1–2 milliseconds each. A project with 3,000 component tests would complete in roughly 3–5 seconds — fast enough to run on every change before pushing to a pull request. End-to-end tests can be reserved for CI/CD pipelines triggered on pull requests.
Getting Started: Tips from Q&A
The session’s Q&A surfaced practical advice for teams starting this journey:
- Use GitHub Copilot — once two or three tests follow the naming and structure pattern (e.g.,
CheckMandatoryHeaderFieldsInvalidPostingDate), Copilot picks up the pattern and generates subsequent tests quickly. - Annotate existing code with TODO comments — walk through the spaghetti codeunit and mark each area that contains custom logic worth testing. This creates a checklist of future components and helps identify which scenarios belong together.
- Start with the test plan — write scenarios first, group related ones, and let the groupings define your components before writing a single line of implementation code.
- Performance tests are separate — SOLID and componentized structures don’t interfere with performance testing; simply run the full flow without injecting spies or using temporary records.
Source Code
All code examples from the webinar — spaghetti, componentized, SOLID, and componentized-SOLID variants — are available on GitHub. The repository uses AL preprocessor symbols (#if compiler directives) to toggle between the different code stages in app.json and test app.json, making it straightforward to compare each approach side by side.
Repository: github.com/lvanvugt/Seminar-Management
📖 Docs: Implement test automation techniques in Business Central — Microsoft Learn — a training module covering test isolation, data setup patterns, and structuring AL tests for maintainability.
This post was drafted with AI assistance based on the webinar transcript and video content.
