This project is read-only.

How to test mappings using the Map Test Framework

This HOWTO explains how to setup and realize a test fixture to test a BizTalk map using the Map Test Framework. Each task is explained by specifying a number of steps to follow. Afterwards, an example is given. The HOWTO follows a test-driven approach for testing maps. It is not mandatory to use this approach.

Setup your test project

Before you can test your map, you have to setup a new test fixture class. The setup is very easy and allows you to specify which template files are being used and which map is being tested.

Follow the next steps to setup a new test fixture class:

  1. Copy the binaries from the bin directory of the MapTestFramework binaries zip file to a directory where you reference your third party assemblies;
  2. Add a reference to the Microsoft.BizTalk.TestTools assembly;
  3. Add a reference to the Microsoft.XLANGs.BaseTypes assembly;
  4. Add a reference to the  MapTestFramework.Common assembly;

Setup a test class

Follow the next steps to setup a test class:

  1. Add a new unit test class;
  2. Inherit from the MappingFixture;
  3. Implement the following members:
    • CreateMap method: return an instance which supports the IMapExecuter interface to test. Use the TestableMapBaseMapExecuter for a BTS2009 project that has unit tests enabled. The TransformBaseMapExecuter can be used for projects that do not have unit tests enabled;
    • SourcePathBase property: return the source path for the file that acts as the base file for all source xml.
    • ExpectedPathBase property: return the expected path for the file that acts as the base file for all expected results. Make sure this file is the result of the map the file specified by the SourcePathBase.
  4. Ensure that the source and expected files are deployed to the test directory. Use the testrunconfig to deploy those files.

Example:

protected override string ExpectedPathBase
{
get { return "Schema2.xml"; }
}
protected override string SourcePathBase
{
get { return "Schema1.xml"; }
}

protected override IMapExecuter CreateMap()
{
return new TestableMapBaseMapExecuter(new Schema1_To_Schema2());
}

Add the base test

It is now time to add the first test which is called the base test. It runs the maps against the file specified by the SourcePathBase property and checks whether the result is the same as the file specified by the ExpectedPathBase property.

Follow the next steps to add the base test:

  1. Link the root elements to each other in the map;
  2. Add a TestBase test method which invokes base.ExecuteBaseTest();
  3. Run the test. It will fail. Add all expected values in the map to the elements using the Value property of the element;
  4. The test will now succeed.

Example:

/// <summary>
/// Tests whether the base source file maps into the base expected file.
/// </summary>
[TestMethod]
public void TestBase()
{
base.ExecuteBaseTest();
return;
}

The test succeeds because we simply added constant values to the target elements. Even though we have not yet realized any of our mappings yet, we now have a base test that should always succeed after realizing any of our mappings. After realizing the base test, we can go through every field, add a test (which fails) and realize the mapping. Of course, the framework does not force you to do it field by field, but it will give more control over your map especially when the map does not respond as expected.

Add additional tests

Follow the next steps to add an additional test:

  1. Add a new test method to your test fixture class. For example: TestField1 for testing source field 'Field1';
  2. Instantiate a new MapTestCases object and specify the source and target elements that participate in the test using an xpath query expression. Note that the xpath query expressions can be easily retrieved by opening the map, selecting the element, and retrieving the value of the Instance XPath property;
  3. Use the AddTestCase method of the MapTestCases to specify the values that should be substituted for the source element and target elements. Specifying a null value will remove the element from the file;
  4. Invoke the TestMap method specifying the instance of the MapTestCollection class as a parameter;
  5. Run the test, which will fail;
  6. Realize the mapping;
  7. The test will now succeed.

Example:

/// <summary>
/// Tests an optional field that maps one to one to a target field.
/// It runs two testcases: one in which the field is removed and the other
/// in which the value has been changed.
/// </summary>
[TestMethod]
public void TestFieldOneToOne()
{
MapTestCases collection = new MapTestCases(
"/*[local-name()='Root' and namespace-uri()='http://MySampleProject.Schemas.Schema1']" +
"/*[local-name()='Record_OneToOne_Optional' and namespace-uri()='']" +
"/*[local-name()='Field_OneToOne' and namespace-uri()='']",
"/*[local-name()='Root2' and namespace-uri()='http://MySampleProject.Schemas.Schema2']" +
"/*[local-name()='Record_OneToOne_Optional' and namespace-uri()='']" +
"/*[local-name()='Field_OneToOne' and namespace-uri()='']"
);
collection.AddTestCase(null, null);
collection.AddTestCase(
new string[] { "NewValue" }, new string[] { "NewValue" }
);
base.ExecuteMapTest(collection);
return;
}

Verify the results

Even though most of us code according to the first time right principle, there is however a small possibility that an error occurs. When the test fails, you can use the detailed Test Results to inspect the error. In case the comparison fails, the exception will indicate in which line/column it fails. Each test case in a single test method uses a separate file for the source, expected and actual file. The location of the files are shown in the detailed Test Results as shown below.

Example:

       ---------------------------------------------------
        Starting test TestFieldOneToOne1
        ---------------------------------------------------
        Source file: C:\Projects\Main\Common\trunk\src\Common\src\TestResults\Administrator_UD-BT06R2 2009-11-01 11_36_17\Out\Schema1TestName1.xml
        Actual file: C:\Projects\Main\Common\trunk\src\Common\src\TestResults\Administrator_UD-BT06R2 2009-11-01 11_36_17\Out\Schema2TestName1_Actual.xml
        Expected file: C:\Projects\Main\Common\trunk\src\Common\src\TestResults\Administrator_UD-BT06R2 2009-11-01 11_36_17\Out\Schema2TestName1_Expected.xml
        ---------------------------------------------------
        Completed test TestFieldOneToOne1
        ---------------------------------------------------
        ---------------------------------------------------
        Starting test TestFieldOneToOne2
        ---------------------------------------------------
        Source file: C:\Projects\Main\Common\trunk\src\Common\src\TestResults\Administrator_UD-BT06R2 2009-11-01 11_36_17\Out\Schema1TestName2.xml
        Actual file: C:\Projects\Main\Common\trunk\src\Common\src\TestResults\Administrator_UD-BT06R2 2009-11-01 11_36_17\Out\Schema2TestName2_Actual.xml
        Expected file: C:\Projects\Main\Common\trunk\src\Common\src\TestResults\Administrator_UD-BT06R2 2009-11-01 11_36_17\Out\Schema2TestName2_Expected.xml
        ---------------------------------------------------
        Completed test TestFieldOneToOne2
        ---------------------------------------------------

Add test for advanced scenario

There are advanced scenarios that are difficult to test, such as testing for the current date. For those cases, the Map Test Framework provides the OnTestExecuting and OnTestExecuted methods. When overridden in your test class, the methods can be used to manipulate the xml files used in the test at the following moments:

  • Just before the map is being executed (OnTestExecuting)
  • Right after the map has been executed but before the results are being asserted (OnTestExecuted).

Both methods have specific usages. The OnTestExecuting method should be used to set dynamic values in the source xml file such as the current date. The updated source xml file with the current date will then be used in the test instead of the pre-defined one. The OnTestExecuted method should be used to assert values that cannot be predicted beforehand such as a new GUID or the current time, and to replace them with a predictable (pre-defined) values.

Follow the next steps to add a test for an advanced scenario:

  1. Add a new test method to your test fixture class.
  2. Override the OnTestExecuting and/or OnTestExecuted methods.
  3. Use the TestEventArgs to get information about the test currently executing such as:
    • The SourcePath property, which returns the path of the source xml file.
    • The OutputPath property, which returns the path of the actual output xml file.
    • The ExpectedPath property, which returns the path of the expected output xml file.
    • The StartDateTime property, which returns the DateTime at which the test was started.
    • The TestName property, which returns the name of the test being executed.

The various path properties can be used to manipulate the xml files used in the test. Use the TestName property to filter the test cases for which manipulation should be done. The StartDateTime can be used for current date and time assertions: determine whether the DateTime value set in the output xml file lies between StartDateTime and DateTime.Now. After the assertion, just replace the value in the output xml file with a pre-defined value.

Example:

/// <summary>
/// Checks a field in the expected xml file for the current datetime
/// </summary>
/// <param name="sender">The object that fired the event.</param>
/// <param name="e">TestEventArgs object that contains context information.</param>
protected override void OnTestExecuted(object sender, TestEventArgs e)
{
CheckCurrentDateTimeAndRemoveNode(e);
CheckGuidAndSubstituteWithConstValue(e);

base.OnTestExecuted(sender, e);
return;
}

private static void CheckCurrentDateTimeAndRemoveNode(TestEventArgs e)
{
const string xpathDateTime = @"/*[local-name()='Root2' and namespace-uri()='http://MySampleProject.Schemas.Schema2']" +
"/*[local-name()='Field_CurrentDateTime' and namespace-uri()='']";
DateTime fullDateTime = DateTime.Parse(
XmlHelper.GetSingleNodeOrThrowException(xpathDateTime, e.OutputPath).InnerXml,
CultureInfo.InvariantCulture, DateTimeStyles.AssumeLocal
);
Assert.IsTrue((fullDateTime >= e.StartDateTime), "DateTime value does not exceed StartDateTime");
Assert.IsTrue((fullDateTime <= DateTime.Now), "DateTime value exceeds DateTime.Now");
XmlHelper.RemoveNodes(xpathDateTime, e.OutputPath, e.OutputPath);
}

private static void CheckGuidAndSubstituteWithConstValue(TestEventArgs e)
{
const string xpathGuidNode = @"/*[local-name()='Root2' and namespace-uri()='http://MySampleProject.Schemas.Schema2']" +
"/*[local-name()='Field_Guid' and namespace-uri()='']";
System.Guid dummy = new Guid(XmlHelper.GetSingleNodeOrThrowException(xpathGuidNode, e.OutputPath).InnerXml);
XmlHelper.UpdateNodes(xpathGuidNode,
"{C483351A-C038-49a5-954C-3AF00AE2D71A}", e.OutputPath,
e.OutputPath
);
}


See the included sample for more details.

Last edited Jan 16, 2011 at 9:16 AM by dm_denheijer, version 2

Comments

dm_denheijer Feb 13, 2011 at 10:23 PM 
Sander,

A test failure is reported as follows:

Test method MapTestSample.Tests.Schema1_To_Schema2Fixture.TestRecordOneToOne threw exception: NUnit.Framework.AssertionException: XmlDiff
XmlUnit.Difference type: 14, control Node: None, test Node: None
At control location (10,21)
At test location (10,21)
Expected: True
But was: False

Although XmlUnit does not give you enough information, the framework shows on which line and in which colum (10, 21) the error occurs. Simply open the actual and expected files at the location mentioned by the test results and go to the specified line and column number. This is also explained in the power point presentation.
However, I think there is some room for improvement on this part. Ideally, you want the file to be opened at the location of the error when clicking the error in the test results window.

Kind regards,
Maurice den Heijer.

sandernefs Jan 16, 2011 at 9:38 PM 
You can simplify the project and a lot by changing the XPath queries without using the namespace uri to, example: / * [local-name () = 'Input']/*[ local-name () =' input value '] "

If there are errors in the mapping is not clear:
XmlUnit.Difference type: 14, Node Control: None, Node test: None. I would like to get detailed information in where the failure occurred.