Test Automation for Connected Systems

In this Areopa Academy webinar (session 104, season 6), Patrick Schiefer — AL developer at COSMO ALPACA and Microsoft MVP for Business Central — walks through practical techniques for writing automated unit tests in AL when your extension depends on external systems. Moderator Gert Hector hosts the session and facilitates a Q&A at the end.

The companion code repository is available on GitHub: PatrickSchiefer/DirectionsEMEA2024-Testautomation.

Challenges and Goals

Business Central environments have grown significantly more complex. Extensions routinely connect to external systems — web shops, EDI solutions, Power Automate flows, external databases, and third-party APIs — and every BC version brings a major update every six months plus monthly minor updates for SaaS environments.

In that context, full manual integration testing before each update is impractical. Key users from older projects may have moved on, leaving gaps in integration knowledge. The result is unpredictable, hard-to-maintain test plans.

Slide listing testing challenges: growing BC environment complexity, update cycle pressure, no time for full integration tests, missing knowledge, and test plan maintenance overhead
▶ Watch this segment

Patrick frames the session around five concrete goals: reduce time for testing, structure code for testability from the start (not as an afterthought), keep code simple (the KISS principle from clean code), enable customers to apply updates quickly, and increase overall quality.

Types of External Connections

Before choosing a test strategy, it helps to recognise the types of connections a BC extension may make:

  • APIs — REST calls exchanging JSON
  • SOAP — older XML-based web services
  • File services — OneDrive, SFTP, and similar
  • Business Events — integration with Power Automate
Slide showing the different types of external connections in a BC ecosystem: APIs (REST/JSON), SOAP/XML, file services, and Business Events (Power Automate)
▶ Watch this segment

Each connection type requires a different test approach, or at least an architecture that accommodates testing.

Three Test Strategies

Patrick compares three strategies commonly used in the BC ecosystem:

System Testing

Testing against live production environments connected to live external systems. It provides the highest assurance that things work in the real world, but it is time-consuming, cannot cover posting scenarios without polluting real data, and results are unpredictable when external data changes between runs.

End-to-End Testing

Similar to system testing but using sandbox environments instead of production. Results are more controllable, but still require a fully configured and deployed release before tests can run. Maintaining the environments and keeping test data consistent across systems adds significant overhead.

Unit Testing

Testing small, isolated chunks of AL code using fake data. Unit tests run entirely within BC, produce highly predictable results, and can catch regressions early — in your local Docker container or as part of a CI pipeline. The trade-off is that unit tests do not verify the actual network connection to the external system.

Slide summarising unit test characteristics: small code chunks, predictable runs, fake data, code stability testing — but not testing the connection between systems
▶ Watch this segment

For most day-to-day development work, unit testing is the most practical option. The session focuses on making unit testing work reliably for code that calls external systems.

The Problem: Direct Dependencies Break Tests

A typical outgoing API call in AL looks straightforward: a codeunit calls a REST client, receives a JSON response, and stores the result. Testing this directly means the test depends on the external system being online and returning consistent data — neither of which can be guaranteed.

AL code slide showing a GetWeatherForecast procedure that calls an external REST endpoint and populates a WeatherForecast record with JSON data
▶ Watch this segment

Patrick demonstrates this with a GetWeatherForecast procedure that queries an external weather API. Running the test codeunit against a system that is offline produces a timeout error — a failure caused by infrastructure, not by a code bug. This kind of flakiness blocks development and undermines confidence in the test suite.

📖 Docs: Call external services with the HttpClient data type — official reference for working with HttpClient in AL, including certificates, cookies, and related test considerations.

Restructuring Code for Testability

The solution is to introduce an abstraction layer between the processing logic and the data transport layer. Instead of calling the REST client directly, the processing codeunit depends on an AL interface that defines what data it needs. The real implementation of that interface makes the actual HTTP call; a mock implementation returns controlled fake data during tests.

Slide explaining how to restructure code for testability: remove direct dependencies, split processing from data transfer, and abstract data transfer for replacement during tests
▶ Watch this segment

The key principles are:

  1. Remove the direct dependency of processing code on external systems.
  2. Split the codeunit into a processing unit and a data-transfer unit.
  3. Abstract data transfer behind an interface so it can be swapped out at test time.

Live Demo: Mocking an Outgoing API

Patrick shows the refactored weather forecast example. The processing codeunit now calls impl.GetWeatherForecast() through an interface. In production, the implementation performs a real REST call. In tests, it is replaced with a mock codeunit.

VS Code showing MockGetWeatherForecast codeunit that implements IGetWeatherForecast interface and returns a hard-coded JSON response with fake weather data
▶ Watch this segment

The mock codeunit (MockGetWeatherForecast) implements the same interface and returns a hard-coded JSON payload using HttpContent.Create. The test initialises the processing codeunit with the mock implementation instead of the real one, then calls the weather forecast procedure and asserts on the result.

With the mock in place, the test passes every time — regardless of whether the external service is up. Multiple mock variants can be created to simulate different scenarios: successful responses, HTTP 500 errors, timeouts, and malformed JSON.

Patrick also presents a more advanced example: placing an order in a third-party system. The test suite for this includes separate tests for a successful order, a failed order (HTTP 500, asserted to throw an error), and a JSON structure check that validates the generated payload independently of any network call.

📖 Docs: Mock outbound HttpClient web service calls during testing — BC 2025 release wave 1 (v26) introduces a built-in [HttpClientHandler] attribute as an alternative to the interface pattern for mocking outgoing HTTP calls in test codeunits.

A Note on HTTPClientHandler (BC 26)

BC 26 introduces a built-in [HttpClientHandler] attribute that provides a similar mocking capability without requiring a custom interface. Patrick explains why he continues to prefer the interface approach: it has worked since BC 16, it is more flexible (it can mock any kind of external dependency, not just HTTP), and it is already battle-tested in production extensions.

Slide noting HTTPClientHandler available in BC26 as a built-in alternative for mocking HTTP calls, with a link to Microsoft documentation
▶ Watch this segment

Summary: Benefits of Mocking

After the outgoing API demo, Patrick summarises the key advantages of the mocking pattern:

  • Tests are fully predictable — they only fail when your code is broken.
  • No dependency on third-party system availability.
  • CI/CD pipelines run cleanly even when external systems are offline.
  • Fail-early feedback during local development in Docker or the web client.

Inbound APIs: The Same Principle

Inbound APIs — where an external system POSTs data into BC — cannot be called directly from AL test codeunits (API pages are not accessible from within test automation). The standard approach of testing via Postman works but cannot be automated in a pipeline.

Architecture diagram showing the inbound API testability pattern: External Solution calls an API Page, which uses a custom (optionally temporary) API Table, processed by a dedicated Codeunit
▶ Watch this segment

The solution Patrick demonstrates is to introduce an intermediate temporary table as the API page’s source table. The API page writes the incoming data into this temporary table and then delegates processing to a codeunit. The codeunit can be called directly from test code, bypassing the API page entirely.

This pattern allows the following test scenarios without any external HTTP client:

  • Test that a valid inbound payload creates the expected record (e.g., a sales order).
  • Test that an invalid payload (e.g., a non-existent customer number) is rejected with the correct error.
  • Deliberately break the processing codeunit and verify the test catches the regression.
📖 Docs: API page type — Microsoft reference for defining API pages in AL, including source tables, triggers, and OData configuration.

Demo: Running the Tests in BC

Patrick runs all tests from the AL Test Tool in the Business Central web client. The results show eight passing tests across two codeunits — weather forecast mocking and place-order scenarios — with two expected failures introduced deliberately to verify that broken code is caught.

AL Test Tool in the Business Central web client showing 8 successful tests and 2 failures across codeunits PlaceOrderTest and TestSalesOrderAPI
▶ Watch this segment

He notes that the same methodology — interface-based abstraction — applies equally to file service integrations, database connections, and any other external dependency. The pattern is not limited to HTTP.

Conclusions

Patrick closes with three takeaways for AL developers working on connected systems:

  1. There is no single right way to test — system, end-to-end, and unit tests all have a place, but unit tests are most practical during active development.
  2. Any test automation you add is better than none. Prioritise automating over manual testing.
  3. Abstract external dependencies behind interfaces so your tests produce predictable results. Without predictability, tests have no value.
📖 GitHub: PatrickSchiefer/DirectionsEMEA2024-Testautomation — the complete sample AL project shown in this webinar, originally presented at Directions EMEA 2024. Includes weather forecast mock, place-order tests, and the inbound sales order API.

This post was drafted with AI assistance based on the webinar transcript and video content.