AL Runner
Writing tests
Published from docs/manual/writing-tests.md in the source repository.
Tests for AL Runner are ordinary Business Central test codeunits. There is no runner-specific framework and no attribute to add. A test that works here also works against a real environment, which is the point: a test that only passed here would say nothing about Business Central.
Apps
Pass your app and its test app, in that order or any order:
al-runner test ./MyApp ./MyApp.Test
Each is a folder with an app.json, a source archive (*.Source.zip), or an
.app that carries its source. The test app lists your app as a dependency in
app.json, as it would for the AL compiler.
Each app sees the AL methods of the runtime its own app.json declares. A call
of a method newer than that runtime gets the AL compiler’s AL0666, and the run stops
with exit 3, as the compiler’s would. For an app whose runtime is older than 16.0,
AL Runner uses a table measured with the AL compiler itself, so it reports the same
AL0666. Where the compiler reports something other than AL0666 for the call (for
example a method only OnPrem apps may use, in an app older than runtime 4.0), the
routine that makes the call does not run, and a note says so.
The same holds for properties, data types, method attributes and object types: a
property the compiler does not accept at the app’s runtime (for example Subtype on a
report layout below runtime 18.0, or ObsoleteState on a page area from runtime 13.0
on) gets the compiler’s AL0666, from a second table measured the same way.
Preprocessor symbols for #if come from --define SYMBOL (repeatable), or
--preprocessor-symbols A,B.
A test codeunit
codeunit 50100 "My Feature Tests"
{
Subtype = Test;
[Test]
procedure DiscountAppliesToTheLineAmount()
var
Assert: Codeunit "Library Assert";
begin
// arrange and act here
Assert.AreEqual(90, ActualAmount, 'A 10% discount should leave 90.');
end;
}
Subtype = Test marks the codeunit and [Test] marks each test procedure. The
Microsoft test libraries (Library Assert, Any, and the rest) are real Business
Central code and run as written. Handler attributes work as on a service tier:
[ConfirmHandler], [MessageHandler], [StrMenuHandler], [ModalPageHandler],
[PageHandler], [RequestPageHandler], [ReportHandler], [SendNotificationHandler]
and [HttpClientHandler]. An unhandled dialog fails the test, as it does on
Business Central. Page and report layout is not rendered; see
What works.
Choosing tests
A test is selected when any selecting option matches it:
al-runner test ./MyApp ./MyApp.Test --filter Posting # name contains "Posting"
al-runner test ./MyApp ./MyApp.Test --filter Codeunit50100.Foo # qualified name
al-runner test ./MyApp ./MyApp.Test --codeunit 50100 # by object id
al-runner test ./MyApp ./MyApp.Test --test-exact Codeunit50100.Foo
al-runner test ./MyApp ./MyApp.Test --exclude-test Slow
--filter TEXT(also--test TEXT): tests whose name contains TEXT, any case. TEXT with a dot is matched againstCodeunit<ID>.<Method>; TEXT without a dot against the method name.--test-exact NAME: the test whose method name orCodeunit<ID>.<Method>is exactly NAME.--codeunit ID: a test codeunit by object id.--exclude-test TEXT: leaves out the tests TEXT matches. It wins over every selecting option.
All of them can be repeated. A selected test’s codeunit runs whole, because the tests of a codeunit share state. Only the selected tests are reported, and only the selected codeunits are compiled. If the selection matches no test, the run exits with 6.
Isolation
al-runner test --isolation codeunit ./MyApp ./MyApp.Test # default
al-runner test --isolation test ./MyApp ./MyApp.Test
codeunit(default): the database is rolled back when a test codeunit ends. This is the default of Microsoft’s test runner.test(alsofunction,method): the database goes back to its state afterOnRunafter every test method. The codeunit’s AL variables are kept, as withTestIsolation = Function.disabledis not supported. Every test is reported as not run.
Test-first work with --tdd
--tdd lets you write the test before the code. A test that calls a procedure
that does not exist yet runs against a stub instead of failing to
compile. The stub returns the default value of the type the call site asks for
(0, '', false, an empty record), writes nothing and never raises. var
arguments stay unchanged.
al-runner test ./MyApp ./MyApp.Test --tdd
- Every test that ran a stub is reported as stubbed, whether it passed or failed.
The output names the stubs, and the
Tests:line counts stubbed passes and failures separately. - A stubbed pass counts as a pass for the exit code.
--output-jsonaddsstubbedandgeneratedStubsper test and in the summary, and--junitadds astubbedproperty.- In test apps, these calls become stubs so far: a procedure that does not exist on a
codeunit, record, page or report variable, on
Rec,CurrPageorthis; a bare call of a procedure the object does not declare; and a call whose argument count no overload takes. A call on a TestPage, an interface or a built-in type stays an error. A call site whose type the use does not reveal is refused, and its test is not run. servetakes it as thetddfield ofrunTests, orserve --tdd.
Setup data
Many Business Central tests expect setup records that an empty database does not have. A run that uses Microsoft’s apps restores the CRONUS demo company by default. See Test data.
Time limit
A test that uses more than 20 seconds of CPU time is stopped at its next loop
iteration and reported as not run, with the limit as the reason. Load on your
machine does not count. A test that hangs without computing is stopped at ten
times the limit in wall time. --test-timeout-secs N changes the limit, and 0
removes it. Later failures in the same codeunit are then not trusted; see
Reading results.
Examples
The AL language test corpus holds thousands of tests of exactly this shape. Each was validated against a real Business Central service tier.