Blog Post

Azure Integration Services Blog
4 MIN READ

Data Mapper Test Executor: A New Addition to Logic Apps Standard Test Framework

WSilveira's avatar
WSilveira
Icon for Microsoft rankMicrosoft
Dec 08, 2025

The Data Mapper Test Executor brings first-class support for map testing directly into the Logic Apps Standard Automated Test Framework. This means faster feedback loops, improved reliability, and a more streamlined developer experience.

Why Does It Matter?

Testing complex data transformations has always been a challenge for Logic Apps developers. With workflows relying on XSLT, Liquid templates, and custom maps, validating these transformations often required manual steps or external tools. The Data Mapper Test Executor changes that by bringing first-class support for map testing directly into the Logic Apps Standard Automated Test Framework. This means faster feedback loops, improved reliability, and a more streamlined developer experience.

Key Highlights

  • Native Support for Data Mapper Testing
    Execute unit tests for XSLT 1.0/2.0/3.0 and Logic Apps Data Mapper (.lml) files without leaving your development environment.
  • Integrated with Automated Test Framework SDK
    Leverages the latest SDK capabilities for consistent test execution and reporting.
  • Enhanced Validation
    Supports schema validation and transformation checks, ensuring your maps behave as expected across scenarios.
  • Performance Optimizations
    Built-in caching and resource management for efficient test runs.

How to Get Started

<PackageReference Include="Microsoft.Azure.Workflows.WebJobs.Tests.Extension" Version="1.0.1" />
  • Add the Data Mapper Test Executor to Your Test Project
    Include the new class in your unit test suite for Logic Apps workflows.
  • Write Your First Test

Check the sample code below with three different methods, highlighting the options that can be used with the DataMapTestExecutor

using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.Azure.Workflows.UnitTesting.Definitions;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using la1.Tests.Mocks.Stateful1;
using Microsoft.Azure.Workflows.UnitTesting;
using System.IO;
using System.Text;
using Microsoft.Azure.Workflows.Data.Entities;
using Newtonsoft.Json.Linq;

namespace la1.Tests
{
    /// <summary>
    /// The unit test class.
    /// </summary>
    [TestClass]
    public class DataMapTest
    {
        /// <summary>
        /// The map to test.
        /// </summary>
        public const string MapName = "source_order_to_canonical_order";

        public string PathToMapDefinition { get; private set; }

        public string PathToCompiledXslt { get; private set; }

        public string PathToXsltTestInputs { get; private set; }

        /// <summary>
        /// The data map test executor.
        /// </summary>
        public TestExecutor TestExecutor;

        [TestInitialize]
        public void Setup()
        {
            this.TestExecutor = new TestExecutor("Stateful1/testSettings.config");
            this.PathToMapDefinition = Path.Combine(this.TestExecutor.rootDirectory, this.TestExecutor.logicAppName, "Artifacts", "MapDefinitions", $"{DataMapTest.MapName}.lml");
            this.PathToCompiledXslt = Path.Combine(this.TestExecutor.rootDirectory, this.TestExecutor.logicAppName, "Artifacts", "Maps", $"{DataMapTest.MapName}.xslt");
            this.PathToXsltTestInputs = Path.Combine(this.TestExecutor.rootDirectory, "Tests", "la1", "Stateful1", "DataMapTest", "test-input.json");
        }

        /// <summary>
        /// Sample comparing compiled XSLT to generated XSLT using map name.
        /// </summary>
        [TestMethod]
        public async Task DataMapTest_GenerateXslt()
        {
            var dataMapTestExecutor = this.TestExecutor.CreateMapExecutor();
            var xsltBytes = await dataMapTestExecutor
                .GenerateXslt(mapName: DataMapTest.MapName)
                .ConfigureAwait(continueOnCapturedContext: false);

            Assert.IsNotNull(xsltBytes);
            Assert.AreEqual(expected: File.ReadAllText(this.PathToCompiledXslt), actual: Encoding.UTF8.GetString(xsltBytes));
        }

        /// <summary>
        /// Sample comparing compiled XSLT to generated XSLT using map content.
        /// </summary>
        [TestMethod]
        public async Task DataMapTest_GenerateXsltWithMapContent()
        {
            var mapContent = File.ReadAllText(this.PathToMapDefinition);
            var generateXsltInput = new GenerateXsltInput
            {
                MapContent = mapContent
            };

            var dataMapTestExecutor = this.TestExecutor.CreateMapExecutor();

            var xsltBytes = await dataMapTestExecutor
                .GenerateXslt(generateXsltInput: generateXsltInput)
                .ConfigureAwait(continueOnCapturedContext: false);

            Assert.IsNotNull(xsltBytes);
            Assert.AreEqual(expected: File.ReadAllText(this.PathToCompiledXslt), actual: Encoding.UTF8.GetString(xsltBytes));
        }

        /// <summary>
        /// Sample running data map using map name and test inputs.
        /// </summary>
        [TestMethod]
        public async Task DataMapTest_RunMap()
        {
            var dataMapTestExecutor = this.TestExecutor.CreateMapExecutor();

            var mapOutput = await dataMapTestExecutor
                .RunMapAsync(
                    mapName: DataMapTest.MapName,
                    inputContent: File.ReadAllBytes(this.PathToXsltTestInputs))
                .ConfigureAwait(continueOnCapturedContext: false);

            Assert.IsNotNull(mapOutput);
            Assert.IsTrue(mapOutput.Type == JTokenType.Object);
            Assert.AreEqual(expected: "FC-20250603-001", actual: mapOutput["orderId"]);
            Assert.AreEqual(expected: "VIP-789456", actual: mapOutput["customerId"]);
            Assert.AreEqual(expected: "NEW", actual: mapOutput["status"]);
        }

        /// <summary>
        /// Sample running data map using generated XSLT content and test inputs.
        /// </summary>
        [TestMethod]
        public async Task DataMapTest_RunMapWithXsltContentBytes()
        {
            var dataMapTestExecutor = this.TestExecutor.CreateMapExecutor();

            var mapContent = File.ReadAllText(this.PathToMapDefinition);
            var generateXsltInput = new GenerateXsltInput
            {
                MapContent = mapContent
            };
            var xsltContent = await dataMapTestExecutor
                .GenerateXslt(generateXsltInput: generateXsltInput)
                .ConfigureAwait(continueOnCapturedContext: false);

            var mapOutput = await dataMapTestExecutor
                .RunMapAsync(
                    xsltContent: xsltContent,
                    inputContent: File.ReadAllBytes(this.PathToXsltTestInputs))
                .ConfigureAwait(continueOnCapturedContext: false);

            Assert.IsNotNull(mapOutput);
            Assert.IsTrue(mapOutput.Type == JTokenType.Object);
            Assert.AreEqual(expected: "FC-20250603-001", actual: mapOutput["orderId"]);
            Assert.AreEqual(expected: "VIP-789456", actual: mapOutput["customerId"]);
            Assert.AreEqual(expected: "NEW", actual: mapOutput["status"]);
        }
    }
}

The default TestExecutor should be updated with a new method, so you can use the same class to create UnitTestExecutor and DataMapTestExecutor instances:

public DataMapTestExecutor CreateMapExecutor()
{
    // Set the path for workflow-related input files in the workspace and build the full paths to the required JSON files.
    var appDirectoryPath = Path.Combine(this.rootDirectory, this.logicAppName);

    return new DataMapTestExecutor(appDirectoryPath: appDirectoryPath);
}

 

Note: This feature is available starting with the latest SDK release. Update your dependencies before making changes to your code, to get full intelisense support.

Limitations

  • Loop Structures: Dynamic execution within repeating structures is not yet supported.
  • Non-Mocked Connectors: All actions in the execution path should be mocked.
  • Unsupported Actions: Integration Account maps, custom code actions, and EDI encode/decode remain out of scope for this release.
  • Preview Caveats: If you’re using private preview components, expect limited testing compared to GA releases.

Learn More

Updated Dec 07, 2025
Version 1.0
No CommentsBe the first to comment