Test Isolation
When we run test in NAV (yes, I said NAV) with the Test Tool, happens a little miracle, all our test data are created in the fly and after running are gone, even if you are creating and posting invoices with Commit statements inside.
The source of this miracle is this property in Test runner Codeunit:
codeunit 130400 "CAL Test Runner"
{
Subtype = TestRunner;
TableNo = "CAL Test Line";
TestIsolation = Codeunit;
When we have a Test runner Codeunit we set Test Isolation level, and you can set it to: Disabled, Codeunit or function. You can read more in this link : https://docs.microsoft.com/en-us/dynamics-nav/testisolation-property
The scenario. All or Nothing.
Sometimes I have problems when we invoke posting routines, that can end partially committed. This is the problem in warehouse shipments. We have next scenario:
- A warehouse shipment with two sales orders inside.
- In shipment posting we get an error in the second order, and then we have the first order posted with a historical warehouse posted shipment. The warehouse shipment remains with the second order items.
- You fix the second sales order and then post the shipment.
- You have two posted warehouse shipments for a single warehouse shipment.
I don´t want this behavior. I want only one posted warehouse shipment and when something is wrong (following my mantra “break it early”), stop execution without any database change.
Checking any posting routine.
For this purpose, I made a generic Code unit able to Test any posting routine and breaking the executing when something is wrong but letting continue if it is right. After this test you can post in real, normal way, without rollback changes. We take advantage of test isolation feature, and this way the test execution doesn´t write in database whether is wrong or right. We can make a fake posting to check if all the code will be success before the real posting with database writing.
The key features of this Codeunit are:
- Set a posting Codeunit number and a record variant, to try the post of the record.
- Create a test function that invoke this generic routine and variant record.
- Write a record in table "CAL Test Line" with our Codeunit number.
- Invoke the test runner with this "CAL Test Line" table record.
- If we receive an error from the test runner we break further execution. Otherwise we continue next statement.
Codeunit breakdown.
We create a Single-instance Codeunit, https://docs.microsoft.com/en-us/dynamics365/business-central/dev-itpro/developer/properties/devenv-singleinstance-property . Later we will see why this property is set this way:
codeunit 69004 "Test Post Anything"
{
Subtype = Test;
SingleInstance = true;
The first test function runs the Codeunit, with a record generic stored in a variant variable.
[Test]
procedure PostAnyThingYouSet()
var
begin
Codeunit.Run(CodeunitNo, RecordToPost);
end;
These two values are global variables, that must keep this state in several instances, that’s the reason we set single instance = true.
var
CodeunitNo: Integer;
RecordToPost: Variant;
And the most important function, the function that call the standard Test runner, with a test line record filled with this Codeunit. We catch the last error and If something goes wrong in execution, we break the flow with a blank error throwing:
procedure RunTheTest(NewCodeunitNo: Integer; NewRecordToPost: Variant)
var
CALTestLine: Record "CAL Test Line";
begin
SetEnvirontToPost(NewCodeunitNo, NewRecordToPost);
CreateSuite(CALTestLine);
ClearLastError();
Codeunit.Run(Codeunit::"CAL Test Runner", CALTestLine);
if GetLastErrorText = '' then
Error('');
end;
Function SetEnvirontToPost set the values of the two global vars, and function CreateSuite insert a "CAL Test Suite" record with the current Codeunit:
local procedure CreateSuite(var CALTestLine: Record "CAL Test Line")
var
begin
with CALTestSuite do begin
Name := SuiteName;
Description := SuiteName;
if Insert() then;
end;
with CALTestLine do begin
"Test Suite" := CALTestSuite.Name;
"Line No." := 10000;
if not Insert() then;
"Line Type" := "Line Type"::Codeunit;
"Test Codeunit" := Codeunit::"Test Post Anything";
Run := true;
Modify();
SetRecFilter();
end;
end;
Example.
I know, it´s a little confusing, but here comes the easy stuff: use this Codeunit:
TestPostAnything.RunTheTest(Codeunit::"Res. Jnl.-Post Line", ResJnlLine);
Codeunit.Run(Codeunit::"Res. Jnl.-Post Line", ResJnlLine);
First we invoke our testing post routine. If all were right we continue and do the real posting of the record. Otherwise user gets the error message and stop execution without real posting.
All the code in https://github.com/JalmarazMartn/AL-Test-Isolation
*This post is locked for comments