When you start working with unit testing in Dynamics 365 Finance and Operations (D365FO), creating a test class is the first step. You have two options when creating a test class:
- Using the automatic Test Class template provided by D365FO
- Creating a Test Class manually for more control over the structure
Using the Automatic Test Class Template
When you create a test class in D365FO, you can choose the Test Class template. This automatically generates the basic structure for your test class, ensuring you have the essential methods and class inheritance set up.
Steps to Create a Test Class Using the Template
- Go to Visual Studio in your D365FO development environment.
- Right-click on your project and select Add > New Item.
- Select Test Class under Dynamics 365.
- The class will automatically extend SysTestCase, and Visual Studio will generate the essential methods for you.
Creating a Test Class Manually
If you want more control over the test class structure or need to create custom test scenarios that aren’t directly supported by the template, you can manually create the test class. Here are the steps:
- Create a New Class:
- Right-click on your project and select Add > New Item.
- Choose Class, then provide a name for your class (preferably ending with “Test”, such as MyFirstTest).
- Extend the SysTestCase Class:
- In your class file, make sure to extend SysTestCase to inherit the necessary test framework functionalities.
- Add Test Methods:
- Every test method should be public, return void, and take no parameters.
- Decorate the test methods with a [SysTestMethod] attribute
- Write Setup and Teardown Methods:
- setup() and tearDown() methods allow you to prepare data before and after each test.
- Optionally, use setupTestCase() and teardownTestCase() for global setup/teardown actions before/after any tests.
Key Components of a Test Class
Extending SysTestCase
public class TestClass extends SysTestCase
{
// The class declaration.
// It's essential to extend `SysTestCase` to create a valid test class.
}
Unit test classes must extend SysTestCase, as it provides essential methods for test setup, assertions, and lifecycle management. Without extending SysTestCase, the class won’t be recognized as a test class by the framework.
Attributes and assertion methods
[SysTestMethod, Hookable(false)]
public void MyTest()
{
// Use the assert* functions to check the outcomes.
this.assertNotNull(null);
}
- [SysTestMethod] Attribute: This marks the method as a test method that the testing framework will recognize. This is how the framework identifies that the method should be executed as part of a unit test.
- assertNotNull: The assertNotNull() method is an assertion that checks if a given value is not null. If the value is null, the test will fail, and an error will be reported. If the value is not null, the test passes without any issues.
- Hookable(false): This attribute indicates that the method can’t be overridden by derived classes, ensuring that the implementation of this method is final.
Test Method Prefix
This is an example of a test method without the [SysTestMethod] attribute, but still treated as a test method due to the test prefix. In general, test methods in X++ can either use the test prefix or [SysTestMethod] attribute.
[Hookable(false)]
public void testSomething()
{
this.assertTrue(false);
}
- The assertTrue method is used to verify that a given condition evaluates to true. It takes a boolean expression as its argument and checks its value: if the expression is true, the test passes, while if the expression is false, the test fails and an error is reported.
Test Setup and Teardown Methods
These methods help manage test execution by setting up required data before a test runs and cleaning up afterward.
public void setup()
{
// This method will be called before every test method.
super();
}
- setup() – Runs before each test method to prepare the environment.
- super() – Ensures any base class setup logic is executed.
public void tearDown()
{
// This method will be called after each test method
super();
}
- tearDown() – Runs after each test method to clean up resources.
public void setupTestCase()
{
// Runs before any test method in the class.
super();
}
- setupTestCase() is executed once before any of the test methods in the class. It’s typically used for setting up any test case-level data or global configurations needed for multiple tests.
public void teardownTestCase()
{
// Runs after all test methods have finished.
super();
}
- teardownTestCase() runs after all the test methods have finished. This is the place to clean up global test case data or perform any finalization steps.
Writing Test Methods
Once you’ve created your test class, the next step is to write test methods. These methods will validate the functionality of the code you’re testing.
Test method structure
Test methods must adhere to the following rules:
- They are public, with no parameters and no return value.
- They can be decorated with a [SysTestMethod] attribute or a test method prefix.
- You should use assertion methods to verify that the code behaves as expected.
Common Assertion Methods
- assertTrue(expression): Verifies that the provided boolean expression evaluates to true. If the expression is false, the test fails, indicating that the expected condition was not met.
- assertFalse(expression): Verifies that the provided boolean expression evaluates to false. If the expression is true, the test fails, signaling that the condition is not as expected.
- assertEquals(expected, actual): Verifies that the expected value is equal to the actual value. If the two values do not match, the test fails, and an error is reported indicating the discrepancy.
- assertNotNull(value): Verifies that the provided value is not null. If the value is null, the test fails, indicating that the object or value wasn’t properly initialized or returned.
Writing a Basic Test Method
Arrange: This step sets up the data needed for the test. In this case, two integers (num1 and num2) are initialized with the values 10 and 20.
Act: This step performs the action you want to test. Here, the addition operation (num1 + num2) is performed, and the result is stored in the variable result.
Assert: This step checks whether the action produces the expected outcome. The assertEquals(30, result) assertion verifies that the result of the addition is 30. If the result is not 30, the test will fail.
[SysTestMethod]
public void testAdditionLogic()
{
// Arrange: Set up data
int num1 = 10;
int num2 = 20;
// Act: Perform the addition
int result = num1 + num2;
// Assert: Validate the result
this.assertEquals(30, result);
}
Testing Business Logic Example
[SysTestMethod]
public void testCustomerAccountCreation()
{
// Arrange: Create a new CustTable record for the customer
CustTable custTable;
custTable.AccountNum = 'Cust002';
custTable.CustGroup = '020'; // Example customer group
custTable.Currency = 'BRL'; // Example currency
custTable.PaymTermId = '10DD'; // Example payment terms
custTable.PaymMode = 'CHEQUE-01'; // Example payment method
ttsBegin; // Begin transaction
try
{
// Insert the customer record into CustTable
custTable.insert();
// Act: Fetch the customer account
CustTable fetchedCustTable = CustTable::find('Cust002');
// Assert: Check if the customer was created successfully
this.assertNotNull(fetchedCustTable);
this.assertEquals('Cust002', fetchedCustTable.AccountNum);
ttsCommit; // Commit transaction if everything is successful
}
catch (Exception::Error)
{
// Rollback the transaction if any error occurs
ttsAbort;
throw error("Test failed due to an error.");
}
}
This method tests creating a customer account by inserting a new record into the CustTable and then verifying that the customer was successfully created. It checks that the account is not null and the account number matches the expected value. If an error occurs, the transaction is rolled back.
Conclusion
Creating a test class in X++ is the foundation of unit testing in D365FO. Whether you choose the automatic template or manually create your test class, ensuring proper structure, setup, and assertions is crucial for reliable tests. By following the best practices outlined in this guide, you can build effective unit tests that improve code quality and stability.