Supporting conditions in print management

Print management in F&O (as well as in Dynamics AX) allows you not just to select the report design, print destination etc., but also do it conditionally, e.g. you may want to use different designs or footer texts for certain customers.

In print management setup, you must first define a default setup (which doesn’t allow conditions) and then you add more configurations below it with conditions.

Print management setup form with the condition highlighted

If you develop your own report that utilizes print management, you must tell the system which table to use for the condition. It’ll then create a query that you can modify by pressing the Select button, and you can add more data sources there by joining tables related to the main one.

There are a few variants of the solution, but the most straightforward is creating a CoC extension of PrintMgmtDelegatesHandler.getQueryTableId() and returning a table ID from there. For example:

[ExtensionOf(classStr(PrintMgmtDelegatesHandler))]
final class PrintMgmtDelegatesHandlerMy_Extension
{
    /// <summary>
    /// Retrieves the table that is used to define queries for the document type.
    /// </summary>
    /// <returns>
    /// The table ID of the table this document type queries at runtime.
    /// </returns>
    protected static TableId getQueryTableId(PrintMgmtDocumentType _docType)
    {
        TableId tableId = next getQueryTableId(_docType);
 
        switch (_docType)
        {
            case PrintMgmtDocumentType::MyReport:
                tableId = tableNum(MyTable);
                break;
        }
 
        return tableId;
    }
}

You may also want to select fields for default ranges that makes the best sense for the report. Again, you can extend a method of PrintMgmtDelegatesHandler class: getQueryRangeFields() in this case.

/// <summary>
/// Retrieves the appropriate query range fields for the current document type.
/// </summary>
/// <returns>
/// A list of <c>fieldnum</c> fields for the appropriate query table that is based on the document type.
/// </returns>
/// <remarks>
/// The <c>fieldnum</c> fields are returned in the order they are intended to be displayed. They all
/// correspond to the table referenced by the <c>getQueryTableId</c> method of the same instance of the
/// <c>PrintMgmtDocType</c> class.
/// </remarks>
protected static List getQueryRangeFields(PrintMgmtDocumentType _docType)
{
    List fields = next getQueryRangeFields(_docType);
 
    switch (_docType)
    {
        case PrintMgmtDocumentType::MyReport:
            fields.addEnd(fieldNum(MyTable, FieldA));
			fields.addEnd(fieldNum(MyTable, FieldB));
            break;
    }
 
    return fields;
}

In the report controller, you need to provide a record for the conditions to be evaluated against. That’s the first argument of PrintMgmtReportRun.load() (or loadPrintSettings() if using FormLetterReport).

SysMock in D365FO

This post is about mock objects for unit testing in Dynamics 365 Finance and Operations, using the existing SysMock library.

Mock objects are used to mimic behaviour of real objects to isolate the parts of code you want to test, to simplify test setup, to simulate specific responses and so on.

SysMock is a library for creating mock objects for X++ classes. The only remark about it that I’ve managed to find is SysMock for Dynamics AX 2009, but the library is shipped with F&O too. The current version isn’t the same as the one for AX 2009 – it’s based on CIL, which couldn’t be the case in AX 2009, and classes mentioned in the blog post (SysMockRepository and SysMockExpect) don’t exist anymore, but the capabilities are still there.

Let me begin with an example.

I’m going to test a class called TestedProcess and mock its component: DataProvider class.

class TestedProcess
{
    DataProvider provider;
 
    void new(DataProvider _provider)
    {
        provider = _provider;
    }
 
    str run()
    {
        return strFmt('Name: %1', provider.getName());
    }
 
    str run2()
    {
        return strFmt('Name: %1', provider.getName('x'));
    }
}
 
class DataProvider
{
    str getName(str _parameter = '')
    {
        return 'default';
    }
 
}

A normal test (without any mocking) might look like this:

[SysTestMethod]
public void basic()
{
    this.assertEquals('Name: default', new TestedProcess(new DataProvider()).run());
}

Now, let’s say that DataProvider reads data from database, from a web service or so and we want avoid this dependency. All we want is DataProvider returning a particular value as we’re currently testing TestedProcess and not not DataProvider. What we could do is creating a new class inheriting from DataProvider, overriding getName() and returning a fixed value. (Of course, the current DataProvider does the same thing, but it would do something more complex in a real implementation.). Then we would use this new class instead of DataProvider when creating an instance of TestedProcess.

The mock framework allows us to do the same thing, but at runtime. So we don’t need to create our own mock class for DataProvider; we ask the mock framework to create it for us, and we define how it should behave, such as that getName() method will return a particular value.

Look at an example:

using Microsoft.Dynamics.AX.SysMock;
 
...
 
[SysTestMethod]
public void mockReturnValue()
{
    // Create a DataProvider mock
    MockRepository mockRepo = new MockRepository(false);
    DataProvider dataProviderMock = mockRepo.CreateMock(classStr(DataProvider), MockObjectType::Strict, false, null, null, null, null, null, null, null, null, null, null);
 
    // Make getName() return 'mock'
    dataProviderMock.getName();
    MethodCall methodCall = mockRepo.LastMethodCall;
    methodCall.Return('mock');
    mockRepo.replayAll();
 
    // Run
    this.assertEquals('Name: mock', new TestedProcess(dataProviderMock).run());
}

Another scenario is testing how the system behaves when a component throws an exception. We just tell the mock framework to throw “Test exception” error in getName().

[SysTestMethod]
public void mockThrowException()
{
    // Create a DataProvider mock
    MockRepository mockRepo = new MockRepository(false);
    DataProvider dataProviderMock = mockRepo.CreateMock(classStr(DataProvider), MockObjectType::Strict, false, null, null, null, null, null, null, null, null, null, null);
 
    // Make getName() throw an exception
    dataProviderMock.getName();
    MethodCall methodCall = mockRepo.LastMethodCall;
    methodCall.Error('Test exception');
    mockRepo.replayAll();
 
    // Run
    this.parmExceptionExpected(true, 'Test exception');
    new TestedProcess(dataProviderMock).run();
}

Mock objects aren’t used just to provide predefined responses, but also to verify that the mocked object was used in the expected way. For example, our tests above assume that getName() is called once and they would fail if it wasn’t called or was called multiple times.

We can also test that a method was called with particular parameter values:

[SysTestMethod]
public void providerCalledWithRightParam()
{
    // Create a DataProvider mock
    MockRepository mockRepo = new MockRepository(false);
    DataProvider dataProviderMock = mockRepo.CreateMock(classStr(DataProvider), MockObjectType::Strict, false, null, null, null, null, null, null, null, null, null, null);
 
    // Set an expectation of getName() to be called with a particular parameter value
    dataProviderMock.getName('x');
    mockRepo.replayAll();
 
    // Run
    new TestedProcess(dataProviderMock).run2();
}

If the method was called with a different parameter value, the test would fail with DataProvider.getName(x); Expected #0, Actual #1.

As you can see, the SysMock works and can spare us creation of a custom mock classes, at least when we don’t need any extra logic there. And of course, I haven’t demonstrated all the features it has.

But there are problems too.

One is the lack of documentation and, most likely, support.

I also miss some features that I consider important. For example, I didn’t find a way to set a return value and ignore input parameters (maybe I missed something). I do it often in my mock objects – for example, I have a method returning a file content for a file ID and a mock returns a static text regardless of the ID. Another thing to improve is the message like Expected #0, Actual #1 – this isn’t enough information to understand what actually failed.

And the library itself doesn’t look prepared to be used in F&O. The file (PackagesLocalDirectory\bin\Microsoft.Dynamics.AX.SysMock.dll) does exist in development environments (both CHE and UDE), but there is no reference in AOT. You can add it, of course. After I did it, I noticed strange behavior of compilation:

  1. In the code editor, everything looks correct. I’m getting code completion, the editor complains about wrong code, accepts correct code.
  2. Project build fails with The “ResolveAssemblyReference” task failed unexpectedly. System.NullReferenceException…. And there are warnings like The primary reference “Microsoft.Dynamics.AX.SysMock, Version=7.0.0.0, Culture=neutral, PublicKeyToken=null” could not be resolved because it was built against the “.NETFramework,Version=v4.8” framework. This is a higher version than the currently targeted framework “.NETFramework,Version=v4.7.2”. Installing .NET Framework 4.8 and using it in TargetFrameworkVersion of the project caused more problems than it solved, so I let it be.
  3. Model build works and tests can be executed afterwards.

For this research, I always built the whole model before running tests (and then confirmed that I wanted to run tests from the last successful build), but it would be inconvenient for real work.

Ultimately, I see SysMock as a great proof of concept, but I’m not too keen to use it in its current form. If the problem with the project build gets resolved (which probably wouldn’t be too difficult for Microsoft), I’d use SysMock for some simpler scenarios. But it can’t cover all my needs and I’d love to be able to enhance its logic.

One could use SysMock as an inspiration and write a new mocking framework, but while the library isn’t huge, it would still be quite a lot of work. Simply copying the decompiled code could be a problem from the perspective of the intellectual property. The ideal solution would be Microsoft publishing source code of SysMock as open source and letting the community to extend it.

Dependency injection in automated tests

When writing automated tests with SysTest framework, you often want to isolate the part you’re testing from the rest of the system. Otherwise your tests may fail for reasons unrelated to what you’re testing, you need to configure things that you aren’t really interested in, you may need external dependencies such as web services to call, it slows down your tests and so on.

A good idea is replacing some components with dummy ones (mocks) just for test purposes. For example, instead of communicating with a real web service, you may have a test mock simulating the communication, such as returning predefined responses. You can then test that your code does what it’s supposed to do: that it sends requests to the web service at the right time and with the right data, it can process responses, it does the right thing in case of a network error (simulating it with the mock is much easier than with the real web service) and so on.

If it’s your code, you can (and should) design it in a way that it allows easily replacing components of the application with different ones. It requires two main things:

  • Defining components and putting them to separate classes. It’s often beneficial to refer to interfaces rather then specific classes.
  • Having a way to decide which implementation of a component will be used.

One example of the latter is passing an instance component A to the constructor (or a setter) of component B. In my example with the web service, I might have MyInvoiceSender class dealing with my business logic, utilizing a proxy class that merely passes messages to and from the vendor web service. When I want to provide a test mock, I might do something like this:

TestProxy proxy = new TestProxy();
MyInvoiceReceiver receiver = new MyInvoiceReceiver(proxy);

This is easy when you instantiate the objects directly from your tests. Unfortunately, it’s not always the case – often it’s another process that creates the objects. It’s because we’re testing a larger block of logic at once or dealing with code that hasn’t been written with testing in mind.

For example, MyInvoiceReceiveClass could instantiate a web service proxy internally, rather then getting it from outside as above. Like this:

class MyInvoiceReceiver
{
    WebServiceProxy proxy = WebServiceProxy::construct();
}

This isn’t exactly an example of loose coupling and it makes test isolation more cumbersome, but there are ways. If it’s my code, I can, for instance, add a delegate that allows tests to provide another object.

internal static IProxy construct()
{
    // Allows automated test to inject a test instance
    var eventHandlerResult = EventHandlerResult::newSingleResponse();
    ProxyFactory::constructDelegate(eventHandlerResult);
 
    if (eventHandlerResult.hasResult())
    {
        return eventHandlerResult.result();
    }
    else
    {
        return new MyProxy();
    }
}
 
static delegate void constructDelegate(EventHandlerResult _result) {}

If it’s standard code that I can’t change, I still can create a CoC extension of construct(), ignore the normal return value and provide my own. There I can utilize a delegate or so as well, to make it configurable at runtime.

If the standard code calls a constructor directly, rather then using a factory method like construct(), then we have a problem. That can’t be extended.

There is yet another approach that may be used to create instances – using SysExtension or SysPlugin frameworks. They use object factories to create an instance based on certain criteria. For example, this is how we can dynamically create an instance of the right child of PurchFormLetter based on the document type (PackingSlip, Invoice etc.).

var attribute = new DocumentStatusFactoryAttribute(_document);
purchFormLetter = SysExtensionAppClassFactory::getClassFromSysAttribute(classStr(PurchFormLetter), attribute);

From testing perspective, these are also places that we can utilize to inject test mocks instead of real objects, so we can isolate what we’re testing, simulate states of the system etc. It’s not something that the frameworks are design to do, but they can be extended.

They use two phases: they find the class to construct based on the parameters and then create an instance of the class. I recommend extending the latter phase, because then you can configure your mock object in various ways (e.g. returning different data for different tests) and check the state after the test (e.g. that a method was called as expected).

This how the test code may look like. It says that if SysExtension determines that RealProxy should be used, it’ll use our proxyMock object instead of creating a new instance of RealProxy.

var proxyMock = new ProxyMock().withThrowException(true);
using (var context = new MySysExtAppClassOverrideContext(classStr(RealProxy), proxyMock))
{
    ...
}

And here is the complete code; it’s relatively straightforward.

final class MySysExtAppClassOverrideContext implements System.IDisposable
{
    MySysExtAppClassOverride classOverride = MySysExtAppClassOverride::construct();
    ClassName classToOverride;
 
    public void new(ClassName _classToOverride, Object _overrideWith)
    {
        classToOverride = _classToOverride;
        classOverride.addOverride(_classToOverride, _overrideWith);
    }
 
    public void Dispose()
    {
        classOverride.removeOverride(classToOverride);
    }
}
 
public final class MySysExtAppClassOverride
{
    static MySysExtAppClassOverride instance;
    Map objectOverrideMap = new Map(Types::String, Types::Class); // base class name -> object
 
    private void new()
    {
    }
 
    public static MySysExtAppClassOverride construct()
    {
        if (!instance)
        {
            instance = new MySysExtAppClassOverride();
        }
 
        return instance;
    }
 
    public void addOverride(ClassName _origClassName, Object _overrideObject)
    {
        // Add override
        objectOverrideMap.insert(_origClassName, _overrideObject);
        // Add also an override object for override class, because the extension framework might have already
        // cached the information about the replacement type.
        objectOverrideMap.insert(classId2Name(classIdGet(_overrideObject)), _overrideObject);
    }
 
    public void removeOverride(ClassName _origClassName)
    {
        if (objectOverrideMap.exists(_origClassName))
        {
            objectOverrideMap.remove(_origClassName);
        }
    }
 
    public Object getForElement(SysExtModelElement _element)
    {
        var appElement = _element as SysExtModelElementApp;
        if (appElement)
        {
            return this.getForClass(appElement.parmAppName());
        }
 
        return null;
    }
 
    public Object getForClass(ClassName _className)
    {
        if (objectOverrideMap.exists(_className))
        {
            return objectOverrideMap.lookup(_className);
        }
 
        return null;
    }
}
 
[ExtensionOf(classStr(SysExtAppClassDefaultInstantiation))]
final class SysExtAppClassDefaultInstantiationMyTest_Extension
{
    public static SysExtAppClassDefaultInstantiation construct()
    {
        var instance = next construct();
 
        if (SysTestRunner::isRunning)
        {
            instance = MySysExtAppClassDefaultInstantiationTestStub::construct();
        }
 
        return instance;
    }
 
}
 
final class MySysExtAppClassDefaultInstantiationTestStub extends SysExtAppClassDefaultInstantiation
{
    internal static MySysExtAppClassDefaultInstantiationTestStub construct()
    {
        return new MySysExtAppClassDefaultInstantiationTestStub();
    }
 
    public anytype instantiate(SysExtModelElement _element)
    {
        Object overrideObj = MySysExtAppClassOverride::construct().getForElement(_element);
 
        if (overrideObj)
        {
            return overrideObj;
        }
        else
        {
            return super(_element);
        }
    }
}
 
[ExtensionOf(classStr(SysExtensionAppClassFactory))]
final class SysExtensionAppClassFactoryMyTest_Extension
{
    public static Object getClassFromSysAttribute(
        ClassName       _baseClassName,
        SysAttribute    _sysAttribute,
        SysExtAppClassDefaultInstantiation _defaultInstantiation)
    {
        var instantiation = _defaultInstantiation;
        if (!instantiation &amp;&amp; SysTestRunner::isRunning)
        {
            // If instation is null, SysExtensionAppClassFactory::getClassInstanceFromCacheBySysExtAttribute()
            // uses System.Activator::CreateInstance() instead of SysExtensionIInstantiationStrategy to create
            // an instance. We can't extend System.Activator and provide custom logic in tests, therefore we
            // want to provide an instantiation object if there is none.
            instantiation = SysExtAppClassDefaultInstantiation::construct();
        }
 
        return next getClassFromSysAttribute(_baseClassName, _sysAttribute, instantiation);
    }
}

If extending the class search phase is sufficient for you, you can extend SysExtensionElementSearchStrategy.search().

Process exceptions inside transactions

D365 F&O doesn’t allow you to catch (most) exceptions inside database transactions. But there is a way how you can work with exceptions there.

For example, I have some logic using Message processor and I noticed that the processor ignores CLR (.NET) exceptions. It silently handles them and they appear neither in infolog nor any other log, which makes understanding of such problems very challenging.

Also, all code of the actual message processing is wrapped in a database transaction (in SysMessageProcessorTask.processBundle()), therefore I can’t catch exceptions on my own and do something with them (such as rethrowing them as X++ exceptions). There is a trick, though.

I’ve put my code to a try block, defined a catch block with a variable for the exception, and then a finally block. Like this:

System.Exception ex;
try
{
    ...
}
catch (ex)
{
    throw;
}
finally
{
    if (ex)
    {    
        error(ex.Message);
    }
}

The body of the catch block isn’t executed, but the ex variable gets filled in – and I can use it in the finally block.

In my actual case, I wanted to put CLR exceptions to infolog and ignore X++ exceptions (which already have infolog entries), therefore also I added a condition for the exception type:

if (ex && !(ex is Microsoft.Dynamics.Ax.Xpp.XppException))
{
    error(ex.Message);
 
    // Log more details to Application Insights
    MyLogger::error(ex.ToString()).withMessageId(myMessageId).send();
}

It’s not something I use often, but it can be extremely helpful in certain special cases.

Details of type initializer exception

My F&O development environment showed an error message when I tried to use Inventory > Transaction on a sales order line.

The type initializer for 'Dynamics.AX.Application.Forms.InventTrans' threw an exception.

This doesn’t say much about the actual cause. Attaching a debugger didn’t help – there was no unhandled exception and Info.add() wasn’t called either.

I was still able to get more information; showing that is the main purpose of the blog post.

In Visual Studio, I went to Debug > Windows > Exception settings and enabled TypeInitializationException.

Exception Settings window with System.TypeInitializationException checked

This means that the debugger interrupts the execution already when the exception occurs, regardless of that it’s handled later.

I have an advantage that I know the name of the exception class producing this particular error message, but if you don’t, I’m sure that internet would help you.

When debugging the code again, the debugger stopped in ClassFactory.formRunClassOnClient() and showed details of the exception.

Exception Thrown window with details of the exception

There are three inner exceptions:

  • ApplicationException: InvalidObjectIDAssignment: No ID could be assigned by runtime
  • ChainOfCommandInitializationException: An exception was encountered while initializing chain of command initializer ‘RealControlPrecisionInventTransForm_Extension_$augmentedProxy$run’ on type ‘Dynamics.AX.Application.ChainOfCommand.ChainOfCommandInitializeClass, Dynamics.AX.ApplicationSuite, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null’.
  • InvalidProgramException: Common Language Runtime detected an invalid program.

This is much more specific. The middle exception tells me that the problem is related to an extension of run() method in RealControlPrecisionInventTransForm_Extension class.

InvalidProgramException suggests that there may be incorrectly compiled code or something like that.

I recompiled the module containing RealControlPrecisionInventTransForm_Extension class (Application Suite) and it did solve the problem. I don’t know what happened that recompilation was necessary, but anyway, I’m happy that I was to locate the problem and solve it.