Find test automation documentation and examples
Before you release your Business Central application, you should test its functionality to ensure it works as expected. Testing is an iterative process. It's important to create repeatable tests, and it's helpful to create tests that can be automated. This unit describes the features in Business Central that help you test the business logic in your application, and it provides some best practices for testing.
Dynamics 365 Business Central includes the below features to help you test your application.
Testing best practices
We recommend the following best practices for designing your application tests.
Test code should be kept separate from the code that is being tested. That way, you can release the tested code to a production environment without releasing the test code.
Test code should test that the code being tested works as intended, both under successful and failing conditions. These are called positive and negative tests. The positive tests validate that the code being tested works as intended under successful conditions. The negative tests validate that the code being tested work as intended under failing conditions.
In positive tests, the test method should validate the results of application calls, such as return values, state changes, or database transactions.
In negative tests, the test method should validate that the intended errors occur, error messages are presented, and the data has the expected values.
Automated tests shouldn't require user intervention.
Tests should leave the system in the same well-known state as when the test started so that you can rerun the test or run other tests, in any order, and always start from the same state.
Test execution and reporting should be fast and able to integrate with the test management system so that the tests can be used as check-in tests or other build verification tests, which typically run on unattended servers.
Create test methods that follow the same pattern.
Initialize and set up the conditions for the test.
Invoke the business logic that you want to test.
Validate that the business logic performed as expected.
Only use hardcoded values in tests when you really need it. For all other data, consider using random data. For example, you want to test the Ext. Doc. No. Mandatory field in the Purchases & Payables Setup table. To do this, you need to create and post a typical purchase invoice. The typical purchase invoice line specifies an amount. For most tests, it doesn't matter exactly what amount. For inspiration, you can use the GenerateRandomCode method in the tests that are included in the TestToolkit folder on the Business Central product media.
Application testing example: Testing purchase invoice discounts
Before you release a customized Dynamics 365 Business Central application to a production environment, you must test the application. This walkthrough demonstrates how to use the test codeunits and test libraries to test an application.
You have modified codeunit 70, Purch-Calc.Discount, which is a codeunit in the CRONUS International Ltd. database. You want to test the functionality of the customized codeunit before you offer the customized application for sale. You create a new test codeunit with new test methods to test the Purch-Calc.Discount codeunit. During development, you use the application test libraries to help write a test with fewer lines of code.
If you want to complete this example, you'll need:
Dynamics 365 Business Central with a developer license.
The CRONUS International Ltd. demo data company.
To have imported the Test Toolkit.
Random test data
You can use codeunit Library – Random to help generate random data for your application tests. Only use hardcoded values in tests when you really need it. For all other data, consider using random data.
For more information, see Random Test Data.
Currently these test libraries aren't available in Business Central Saas. They can be installed in Business Central on-premises and in Docker containers.
For more information, see Environment testing support and limitations.
Task 1: Create the test codeunit and method
Create a new codeunit and specifies that it's a test codeunit.
Define the scenario that you want to verify and add a test method to test the Purch-Calc.Discount functionality. By default, in test codeunits are test methods unless you specify otherwise.
In this example, the name of the test method consists of the tested functionality, Purchase Invoice Discount Calculation, and relevant parameters that affect the test result. We recommend that you follow this naming pattern for your test methods also. In our example, the following parameters are introduced.
PInv for the document that is tested, purchase invoices. You can apply the same test to purchase orders or purchase credit memos. Also, we recommend that you have mirrored sets of tests for the sales area.
Above. It's a good practice to have tests for positive and negative scenarios. In this test, Isaac wants to check that discounts come into effect when the document amount is above a minimum amount. But, the amount in the document can be also less than or equal to the minimum amount.
A developer first defines the test scenario [SCENARIO], then details it with the GIVEN-THEN-WHEN notation. Finally, they add the AL code. The code in this test method prepares the test data by setting a random discount percent, a minimum amount, and a document amount. Then, it creates a purchase document with a line and runs the Purch-Calc.Discount codeunit, which contains the code that is being tested. Finally, it verifies the results of running the Purch-Calc.Discount codeunit and raises an error if the results aren't as expected.
You can create additional test methods in this test codeunit to test other aspects of vendor discounts. These test methods should include negative tests, which validate that the code being tested works as intended under failing conditions.
Task 2: Create a helper method
The next task is to create a helper method that generates data for the test.
The helper method can be reused if you decide to extend test coverage.
The code in the helper method prepares data for the test by creating a new vendor, setting up the invoice discount, and creating a purchase document with an item. Because this helper method isn't specific to the test itself, you can reuse it for similar tests. For example, you can call it with other parameters and create a purchase credit memo, or set up a % discount, or create a document where the total amount is less than the minimum amount that is specified in the Vendor Invoice Disc. table.
This test code doesn't guarantee that the state of the database after you run the test is the same as the state of the database before you run the test.
codeunit 50111 "ERM Vendor Discount"
{
// Specifies the codeunit to be a test codeunit
Subtype = Test;
trigger OnRun()
begin
end;
// Makes the method a test method
[Test]
// Adds the test logic to the test method
procedure PurchInvDiscCalculationPInvAbove()
var
PurchLine: Record "Purchase Line";
MinAmount: Decimal;
DocAmount: Decimal;
DiscountPct: Decimal;
PurchCalcDisc: Codeunit "Purch.-Calc.Discount";
begin
// [SCENARIO] "Inv. Discount Amount" should be calculated on Purchase Invoice (in LCY), where Invoice amount is
// above the minimal amount required for invoice discount calculation.
// [GIVEN] Vendor with invoice discount percentage "D" for minimal amount "A" in LCY
// [GIVEN] Create purchase invoice with one line and amount >"A"
DiscountPct := RandomNumberGenerator.RandDec(100, 5);
MinAmount := RandomNumberGenerator.RandDec(1000, 2);
DocAmount := MinAmount + RandomNumberGenerator.RandDec(100, 2);
CreatePurchDocument(PurchLine, PurchLine."Document Type"::Invoice, DocAmount, MinAmount, DiscountPct);
// [WHEN] Calculate invoice discount for purchase document (line)
PurchCalcDisc.RUN(PurchLine);
// [THEN] "Inv. Discount Amount" = Amount "A" * discount "D" / 100
PurchLine.FIND;
Assert.AreEqual(ROUND(PurchLine."Line Amount" * DiscountPct / 100), PurchLine."Inv. Discount Amount", PurchInvDiscErr);
end;
// Creates the test helper method
local procedure CreatePurchDocument(var PurchLine: Record "Purchase Line"; DocumentType: Option; DocAmount: Decimal; MinAmount: Decimal; DiscountPct: Decimal)
var
VendorInvoiceDisc: Record "Vendor Invoice Disc.";
PurchaseHeader: Record "Purchase Header";
VendorNo: Code[30];
begin
// Create vendor
VendorNo := LibraryPurchase.CreateVendorNo;
// Create vendor invoice discount
VendorInvoiceDisc.INIT;
VendorInvoiceDisc.Code := VendorNo;
VendorInvoiceDisc.VALIDATE("Currency Code", '');
VendorInvoiceDisc.VALIDATE("Minimum Amount", MinAmount);
VendorInvoiceDisc.VALIDATE("Discount %", DiscountPct);
VendorInvoiceDisc.INSERT(TRUE);
// Create purchase line
LibraryPurchase.CreatePurchaseDocumentWithItem(PurchaseHeader, Purchline, DocumentType, VendorNo, '', 1, '', 0D);
PurchLine.VALIDATE("Direct Unit Cost", DocAmount);
PurchLine.MODIFY(TRUE);
end;
var
RandomNumberGenerator: Codeunit "Library - Random";
LibraryPurchase: Codeunit "Library - Purchase";
Assert: Codeunit Assert;
myInt: Integer;
PurchInvDiscErr: TextConst ENU = 'The Purchase Invoice Discount Amount was not calculated correctly.';
}