Share This Post

Microsoft Dynamics 365 F&O SSRS Reports Testing

There are various strategies for testing the content of reports using automation. In this article we look at how we can perform Microsoft Dynamics 365 F&O SSRS Reports Testing and render an SSRS report in memory and then query an XML representation of its content using X++.

If we develop a new or extend an existing SSRS report, we should add some further tests to our delivery pipeline to make sure it behaves as the user expects and to automatically pick up on any future regressions.

Microsoft Dynamics 365 F&O SSRS Reports Testing Steps

  • This approach is to test the content of the report once it has been rendered by SQL Server Reporting Services (SSRS) and converted into an XML format.
public void run()
{
    //Configure the SrsReportRunController controller
    controller.parmReportName(reportName);
    controller.parmShowDialog(false);
    controller.parmLoadFromSysLastValue(false);
    controller.parmReportContract().parmReportServerConfig(SRSConfiguration::getDefaultServerConfiguration());
    controller.parmReportContract().parmReportExecutionInfo(executionInfo);

    //Relate the Rdp contract with pre-populated values if we have one
    if(contract)
    {
        controller.parmReportContract().parmRdpContract(contract);
    }
    
    //Build the print settings to render the output to XML
    SRSPrintDestinationSettings settings = controller.parmReportContract().parmPrintSettings();
    settings.printMediumType(SRSPrintMediumType::Custom);
    settings.fileFormat(SRSReportFileFormat::XML);

    //Bootstrap the SRSReportRunService
    srsReportRunService.getReportDataContract(controller.parmReportContract().parmReportName());
    srsReportRunService.preRunReport(controller.parmReportContract());

    //Marshall the parameters from the contract
    Map reportParametersMap = srsReportRunService.createParamMapFromContract(controller.parmReportContract());
    Microsoft.Dynamics.AX.Framework.Reporting.Shared.ReportingService.ParameterValue[] parameterValueArray =
        SrsReportRunUtil::getParameterValueArray(reportParametersMap);

    //Render the report to a byte array
    SRSProxy srsProxy = SRSProxy::constructWithConfiguration(controller.parmReportContract().parmReportServerConfig());
    System.Byte[] reportBytes = srsproxy.renderReportToByteArray(controller.parmreportcontract().parmreportpath(),
        parameterValueArray,
        settings.fileFormat(),
        settings.deviceinfo());
        
    if (reportBytes)
    {
        //Create an XMLDocument from the byte array
        System.IO.MemoryStream stream = new System.IO.MemoryStream(reportBytes);
        xmlDocument = XmlDocument::newFromStream(stream);
    }
    else
    {
        throw Exception::Error;
    }
}
  • The report page model is held in memory by an XmlDocument object. For each report that we want to test, we can add further concrete pages with report implementation specific details:
public class wwSysUserRoleInfoReportPage extends wwBaseReportPage
{
    protected void new()
    {
        this.parmReportName(ssrsReportStr(SysUserRoleInfo, Report));
    }

    public static wwSysUserRoleInfoReportPage construct()
    {
        return new wwSysUserRoleInfoReportPage();
    }

    public void run()
    {
        super();

        //Add any further required clean-up tasks here.. e.g.
        //srsReportRunService.cleanUpRdpPreProcessTables(
        //     classStr(SalesInvoiceDP), executionInfo);
    }

    //Summarize the element names here so the test does not 
    //need to know about the implementation.  This will improve 
    //maintenance if the design changes

    public int roleCount()
    {
        return this.elementCount('RoleName_0');
    }

    public boolean isRoleIncluded(str role)
    {
        return this.elementExists('RoleName_0', 'userID1', role);
    }
}
  • And our tests can ask the pages about the values rendered in the reports without knowing about the underlying implementation details.
public class wwReportExampleTest extends SysTestCase
{
    [SysTestMethodAttribute]
    public void TestReport()
    {
        //Arrange
        wwSysUserRoleInfoReportPage report = 
          wwSysUserRoleInfoReportPage::construct();

        //Pass arguments here if the report has a contract 
        //and vary according to the test
        //SysUserLicenseCountRDPContract contract = new 
            SysUserLicenseCountRDPContract();
        //e.g.
        //contract.parmReportStateDate(today());
        //report.parmRdpContract(contract);

        //Act
        report.run();

        //Assert
        //Make sure the report lists out all 33 
        //(or however many) expected users
        this.assertEquals(33, report.roleCount());

        //Let's make sure the HR assistant appears on the 
        //report as expected
        this.assertEquals(true, 
          report.isRoleIncluded('Human resource assistant'));
    }
}

Testing D365FO using Type Providers and the Page Object Model

The Type Provider framework for Dynamics 365 Finance Operations (D365FO) provides a way for automated integration tests to interface with forms, without the necessity for web browser automation products like Selenium.

Automated tests that require web browser automation can be slow to execute, fragile and expensive to maintain. This is something to avoid if you want your tests to recoup the upfront investment.

Steps for Encapsulating the Type Provider Framework using the Page Object Model

  • Keep the tests as simple, readable and maintainable as possible.
  • Protect our automated test investment from
    • Future changes to the D365FO test frameworks
    • Refactoring of existing functionality
    • Implementation updates from 3rd party ISVs
  • Allow test engineers to define tests and stub the page model whilst feature engineers develop the functionality and fill in the stubs.

The Page Object Model

  • The PageContext wraps the ClientContext and is the starting point for navigation through the application.  It also sets up the persona so that role-based authorization is applied during the test.
  • The Pages execute and will give results. Each Page will wrap one or more FormAdaptorTypeProviders.
  • In the case of a form that consists of other quick forms or form parts, it might make sense to maintain a single page for all of these type providers depending on how much reuse would be possible in maintaining a page for each.
  • Each page extends a BasePage which contains common functionality.
  • The Tests drive the pages and determine between right and wrong (actual vs expected). Tests extend a base test class which is somewhere else to hold reusable functionality across all tests.

Writing Tests

Each test should make its intention clear and should be easy to understand and maintain to prevent it falling into disrepair.

Creating Fixtures

The TestFixtureHelpers are responsible for setting up dependent data to ensure there is a well-known and fixed environment for the tests to run.  This in turn helps to ensure that test results are repeatable.

The TestDataHelper supplies the reference data and other unique and randomly generated values to be used by the fixture helper.

This avoids hard coding magic strings within tests and makes the test suite portable so that it can execute against different target deployments with differing customer configuration.

Adding Pages

The methods on the pages either return a referral to the page itself, or a reference to a different page. This simplifies the navigation experience and enables the fluent API style.

For more information on Microsoft Dynamics 365 F&O SSRS Reports Testing, please contact us.

Share This Post

Leave a Reply

avatar
  Subscribe  
Notify of
Skip to toolbar