Super Fast Tests Covering 100% of Your Code

In this Areopa Academy webinar, Finn Pedersen — a modern Business Central developer who has presented this topic at events including NAV Directions and Days of Knowledge — walks through a concrete coding pattern that makes it possible to write automated tests covering 100% of application code paths, with tests that complete in milliseconds. The approach is based on Environment Interfaces, a technique Finn credits to the BC community, particularly Vjekoslav Babic (Vjeko), and which he refined through building a real AppSource app for an online payment integration.

Why 100% Code Coverage Matters

Finn draws a clear distinction: the goal is not to test every object in a project, but to cover 100% of the code paths in the objects that are tested. Reaching that bar forces developers to think more carefully about code organisation, separation of concerns, and function granularity. It also makes AI-assisted development more reliable — consistent coding patterns allow AI tools to reproduce the same patterns when generating new code or tests.

A key motivating scenario is the payment integration itself: the app must never send real payment requests when running in a sandbox or test environment. Testing all paths through that logic — including the production path — is impossible without the ability to simulate different environment states from within a test.

📖 Docs: Test Codeunits and Test Methods – Microsoft Learn — official reference for structuring automated tests in Business Central AL.

Context Interfaces vs. Environment Interfaces

Finn introduces a distinction between two types of interfaces:

  • Context Interfaces — the standard AL pattern used in the base application (e.g., the Number Series Single interface with an extensible enum of implementations). These allow different implementations depending on a runtime context value.
  • Environment Interfaces — interfaces that wrap the surroundings an app depends on: the underlying platform (sandbox vs. production), the base application, and external APIs. These are the key to achieving 100% code coverage.

An IEnvironment interface might expose three procedures: SystemEnvironment(), CurrentCompanyName(), and IsEvaluationCompany(). The production implementation reads real system values; the test stubs return fixed, controlled values.

📖 Docs: Interfaces in AL – Microsoft Learn — covers the interface keyword, the implements keyword on codeunits, and extensible enums used to select implementations.

Making Impossible States Impossible

A recurring theme is encoding state in enums rather than booleans. The production environment can return either Production or Sandbox — two booleans that theoretically allow four states are collapsed into the two that are actually valid. This reduces the number of code paths and eliminates a class of bugs caused by inconsistent boolean combinations.

The app’s own state follows the same principle: instead of an IsActive boolean, an enum distinguishes NotActivated, Test, and Production — a “three-state boolean” that cannot express an illegal combination.

Stubs and Spies: London Style Terminology

Finn adopts the test-double terminology associated with London-style (mockist) testing:

  • Stub — a fake object that returns fixed data and does not track whether it was called.
  • Spy — like a stub, but it records which functions were called so the test can assert on that afterwards.

A Stub Production Environment codeunit implements IEnvironment and always returns Production, "Production Company", and false for IsEvaluationCompany. Passing this stub into the function under test lets a test assert that the app enters the correct production state — even though the test is running in a sandbox.

📖 Reference: Testing in Isolation – Vjeko.com — Vjekoslav Babic’s post on decoupling AL code for testability using interfaces, which is the foundational inspiration for Finn’s approach.

Testing API Calls Without Hitting the Network

The second environment interface in the demo is IApiRequest, which wraps the HTTP client send operation. The production implementation sends real HTTP requests; the test implementation (Spy Api Request) accepts pre-configured response data — status code, response body, and whether the call should simulate failure — and records that the send function was actually invoked.

This allows full coverage of all logic that surrounds the API call:

  • Request payload encoding and decoding
  • Handling of successful responses (status 200, JSON parsing)
  • Handling of failures (connection errors, unexpected status codes)
  • Correct substitution of test email/phone for customer contact details when not in production

None of these tests touch the real API, so they are safe to run in a build pipeline on every commit.

Eliminating Database Calls from Inner Functions

The “super fast” part of the title comes from a complementary pattern: database reads are performed once at the top of the call hierarchy and passed down by reference (using var) rather than being repeated inside lower-level functions. A function that previously called Setup.Get() internally is refactored to accept the Setup record as a parameter.

The result is that inner functions have no database side-effects — they become close to pure functions in the functional-programming sense. Tests can pass in a temporary record directly, skipping the insert-get-rollback dance entirely. Finn reports running over 200 tests in milliseconds using this pattern.

Code Architecture and the GitHub Repository

The demo project is structured as a workspace with a core app, a payment app, and corresponding test apps for each. Records that travel through multiple functions are declared once at the top level and passed by reference throughout — keeping the function signatures predictable and the AI tooling able to follow the pattern consistently.

When side effects (such as writing to an event log) threatened the purity of the pattern, Finn decoupled them using AL event publishers and subscribers, keeping the testable logic free of hidden state changes.

The full source code is available on GitHub: finnpedersenfrance/BC-Environment-Interfaces.

Finn has also written a four-part blog series covering the pattern in depth:

Q&A Highlights

AI and these patterns. Finn noted that because all functions are pure and consistently structured, AI tools (he uses Claude and Copilot in VS Code and Cursor) can reliably reproduce the pattern. He uses AI heavily for writing test functions, pointing it at the production function and asking it to generate coverage. Running tests automatically as part of an AI coding loop is a direction he sees as increasingly valuable.

Parameter explosion (“parameter hell”). Audience member Foluke raised the concern that promoting database reads to parameters could result in a huge number of parameters at the top-level function. Finn acknowledged this as a valid signal: when it happens, it usually indicates a design that can be improved — either by introducing an interface, splitting into separate apps, or grouping related data into a record (such as an API operation record that carries the endpoint, method, payload, and response as a single unit).

Pure functions in AL. Hilmar Espen asked for a definition. Finn described a pure function as one with no side effects — given the same inputs, it always returns the same output without modifying external state. In AL this is an approximation (temporary records and database operations exist), but the pattern pushes as much logic as possible into side-effect-free procedures that are trivial to test.


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