Share via


BizTalk: Unit test maps using mocks for extension objects

Introduction

Suppose that you have a BizTalk map that relies on database related functoids or external assemblies you would like to isolate using mocks/stubs to unit test, this blog shows you how.

Applies To

  • BizTalk 2013 R2

Overview

BizTalk maps are internally converted to XSLT during BizTalk project compilation. Within the XSLT, all the BizTalk functoids defined in the map are converted to either a CSharp function within the script tag <msxsl:script>  or referenced as external assemblies using extension objects defined in an extensions xml document. When the map is executed, BizTalk uses XslCompiledTransform to load up the xslt and transforms the source xml message.
XslCompiledTransform, when transforming, attempts to create an instance of the class, defined in the extension object, using the default parameter less constructor. 
In order to mock any class methods in external assemblies defined in the extensions xml document, we just need to pass in pre-instantiated mock objects with methods whose signature exactly matches one used by the BizTalk map(the system under test).

Sample Code

 This is the sample map that is used in this test.

When you validate this map in Visual Studio, the output XSLT looks like this. All functions prefixed with ScriptNS0 , mapped to the namespace http://schemas.microsoft.com/BizTalk/2003/ScriptNS0, belong to the class Microsoft.BizTalk.BaseFunctoids.FunctoidScripts.

<?xml version="1.0" encoding="UTF-16"?> 
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:msxsl="urn:schemas-microsoft-com:xslt" xmlns:var="http://schemas.microsoft.com/BizTalk/2003/var" exclude-result-prefixes="msxsl var s0 ScriptNS0" version="1.0" xmlns:s0="http://MockingBiztalkMaps/SourceSchema" xmlns:ns0="http://MockingBiztalkMaps/DestinationSchema" xmlns:ScriptNS0="http://schemas.microsoft.com/BizTalk/2003/ScriptNS0"> 
  <xsl:output omit-xml-declaration="yes" method="xml" version="1.0" /> 
  <xsl:template match="/"> 
    <xsl:apply-templates select="/s0:Root" /> 
  </xsl:template> 
  <xsl:template match="/s0:Root"> 
    <xsl:variable name="var:v3" select="string(ValueToLookUp/text())" /> 
    <ns0:Root> 
      <xsl:variable name="var:v1" select="ScriptNS0:DBLookup(0 , string(ValueToLookUp/text()) , "Provider=SQLOLEDB.1;Integrated Security=SSPI;Persist Security Info=False;Initial Catalog=WAREHOUSE;Data Source=localhost" , "dbo.CALENDAR" , "CALENDAR_ID")" /> 
      <xsl:variable name="var:v2" select="ScriptNS0:DBValueExtract(string($var:v1) , "CALENDAR_DATE")" /> 
      <xsl:attribute name="TranslatedValue"> 
        <xsl:value-of select="$var:v2" /> 
      </xsl:attribute> 
      <xsl:variable name="var:v4" select="ScriptNS0:DBLookup(0 , $var:v3 , "Provider=SQLOLEDB.1;Integrated Security=SSPI;Persist Security Info=False;Initial Catalog=WAREHOUSE;Data Source=localhost" , "dbo.CALENDAR" , "CALENDAR_ID")" /> 
      <xsl:variable name="var:v5" select="ScriptNS0:DBErrorExtract(string($var:v4))" /> 
      <Error> 
        <xsl:value-of select="$var:v5" /> 
      </Error> 
    </ns0:Root> 
    <xsl:variable name="var:v6" select="ScriptNS0:DBLookupShutdown()" /> 
  </xsl:template> 
</xsl:stylesheet>

 The map validation output also creates an extension xml file, and looks like this. This contains the mapping from the namespace to the class, in this instance the namespace http://schemas.microsoft.com/BizTalk/2003/ScriptNS0 is mapped to the  FunctiodScripts class defined in the assembly Microsoft.BizTalk.BaseFunctiods.dll

<ExtensionObjects> 
  <ExtensionObject Namespace="http://schemas.microsoft.com/BizTalk/2003/ScriptNS0" AssemblyName="Microsoft.BizTalk.BaseFunctoids, Version=3.0.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" ClassName="Microsoft.BizTalk.BaseFunctoids.FunctoidScripts" /> 
</ExtensionObjects>

 In order to mock the methods used in the XSLT that belong to classes defined in the extensions, in this example methods DBLookup, DBValueExtract, DBErrorExtract and DBShutdown in class Microsoft.BizTalk.BaseFunctoids.FunctoidScripts, we need to pass a substitute mock object. This object should contain the signature for all the methods that we need to mock, and passed as an argument to XSLTCompiledTransform as the object to use for the namespace http://schemas.microsoft.com/BizTalk/2003/ScriptNS0.

This example below uses Moq to create mocks and Nunit as the test framework,

using System; 
using System.Collections.Generic; 
using System.IO; 
using System.Linq; 
using System.Xml; 
using System.Xml.Linq; 
using System.Xml.Xsl; 
using Map; 
using Microsoft.XLANGs.BaseTypes; 
using Moq; 
using NUnit.Framework; 
  
namespace Tests.UnitTests.Map 
{ 
    [TestFixture] 
    public class  TestMapDbLookup 
    { 
        //Step 1: Define interface containing the signatures for all the functions you want to mock 
        /// <summary> 
        ///     Defines the signature for all functions need to be mocked for unit testing MapDbLookup. 
        ///     You can workout the signature of the assembly in the xslt output when you validate the map in Visual Studio 
        /// </summary> 
        public interface  IMockSignaturesMapDbLookup 
        { 
            /// <summary> 
            ///     Mock signature for Biztalk DbLookup functiod 
            /// </summary> 
            string DBLookup(int indexAutoGenInternal, string valueToLookUp, string connectionString, string tableName, 
                string columnToSearchLookupValueIn); 
  
            /// <summary> 
            ///     Mock signature for Biztalk DB Value Extract functiod 
            /// </summary> 
            string DBValueExtract(string dbLookUpReturnIndex, string columnContainingTranslatedValue); 
  
            /// <summary> 
            ///     Mock signature for Biztalk DB error functiod 
            /// </summary> 
            string DBErrorExtract(string dbLookUpReturnIndex); 
  
  
            /// <summary> 
            ///     Mock signature for Biztalk internal DBLookupShutdown functiod 
            /// </summary> 
            string DBLookupShutdown(); 
        } 
  
  
  
        [SetUp] 
        public void  TestInit() 
        { 
            _sut = new  MapDbLookup(); 
            _mocksForMapDb = new  Mock<IMockSignaturesMapDbLookup>(); 
            //Step 2: Set up your mock object and map the namespace defined in the extensions xml file to this mock object 
            var nameSpace = GetNameSpaceForClass("Microsoft.BizTalk.BaseFunctoids.FunctoidScripts", 
                _sut.XsltArgumentListContent); 
            _namespaceToInstanceObjectsMap = new  Dictionary<string, object> {{nameSpace, _mocksForMapDb.Object}}; 
        } 
  
        //Step 3: The core work magic here 
        private static  void Map(TransformBase map, string xmlInputFile, string outputXmlFile, 
            Dictionary<string, object> namespaceToInstanceObjectsMap) 
        { 
            //Construct XsltArgumentList mapping the namespace to the mocked object 
            var argumentsInstanceExtensionObjects = new  XsltArgumentList(); 
            foreach (var entry in namespaceToInstanceObjectsMap) 
            { 
                argumentsInstanceExtensionObjects.AddExtensionObject(entry.Key, entry.Value); 
            } 
  
            //Do the transform using XslCompiledTransform 
            var transform = new  XslCompiledTransform(); 
            var setting = new  XsltSettings(false, true); 
            transform.Load(XmlReader.Create(new StringReader(map.XmlContent)), setting, new XmlUrlResolver()); 
            using (var stream = new FileStream(outputXmlFile, FileMode.CreateNew))  
            { 
                //using XslCompiledTransform to transform the source to output message using mocked extension objects
                transform.Transform(xmlInputFile, argumentsInstanceExtensionObjects, stream); 
            } 
        } 
  
  
        private Mock<IMockSignaturesMapDbLookup> _mocksForMapDb;  
        private MapDbLookup _sut; 
        private Dictionary<string, object> _namespaceToInstanceObjectsMap; 
  
        private const  string ConnStrDoNotHardCodeThisInYourMap =  
            "Provider=SQLOLEDB.1;Integrated Security=SSPI;Persist Security Info=False;Initial Catalog=WAREHOUSE;Data Source=localhost"; 
  
                      //Your regular test method
        [TestCase("SampleTesttData.xml", "18991231",  "1899-12-31 00:00:00.000")] 
        public void  ShouldMap(string  inputXmlFile, string  lookupValue, string  mockDataTranslatedLookupValue) 
        { 
            //Arrange 
            inputXmlFile = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, inputXmlFile); 
            const int  index = 0; 
            const string  tableName = "dbo.CALENDAR"; 
            const string  searchColumnName = "CALENDAR_ID"; 
            const string  translatedValueColumnName = "CALENDAR_DATE"; 
            //Set up mock..  
            //mock dblookup 
            _mocksForMapDb.Setup( 
                x => x.DBLookup(index, lookupValue, ConnStrDoNotHardCodeThisInYourMap, tableName, searchColumnName)) 
                .Returns(index.ToString); 
            //mock dbvalue extract 
            _mocksForMapDb.Setup(x => x.DBValueExtract(index.ToString(), translatedValueColumnName)) 
                .Returns(mockDataTranslatedLookupValue);

            var actualOutputXmlFile = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()); 
            //mock dberrror 
            _mocksForMapDb.Setup(x => x.DBErrorExtract(index.ToString())) 
                .Returns(""); 
            _mocksForMapDb.Setup(x => x.DBLookupShutdown()) 
                .Returns(""); 
  
  
            //Act 
            Map(_sut, inputXmlFile, actualOutputXmlFile, _namespaceToInstanceObjectsMap); 
  
            //Assert 
            //Can also do  xml compare of the actual vs expected xml file.. 
            Assert.IsTrue(File.Exists(actualOutputXmlFile)); 
        } 
  
        private static  string GetNameSpaceForClass(string classFullName, string xsltExtensionXml) 
        { 
            var root = XElement.Parse(xsltExtensionXml); 
            var namespaceOfScript = 
                from el in  root.Descendants("ExtensionObject") 
                where (string) el.Attribute("ClassName") == classFullName 
                select el.Attribute("Namespace").Value; 
  
            return namespaceOfScript.First(); 
        } 
  
      
    } 
}

The sample xml file, SampleTestTData.xml, used in this test .  The value to lookup 18991231 is mapped to 1899-12-31 00:00:00.000 in mock test data set up, in the test above.

<ns0:Root xmlns:ns0="http://MockingBiztalkMaps/SourceSchema"> 
  <ValueToLookUp>18991231</ValueToLookUp> 
</ns0:Root>

The output of xml file (found in %temp% location defined in test) generated by this test looks like this using the mocked object...

<ns0:Root TranslatedValue="1899-12-31 00:00:00.000" xmlns:ns0="http://MockingBiztalkMaps/DestinationSchema"> 
  <Error></Error> 
</ns0:Root>

To download this source code, see github