The out of the box unit test framework for BizTalk maps leaves a lot to be desired. Effectively a Visual Studio map unit test is the equivalent of choosing to validate your map, which involves running an instance of an input message through your map and validating the output of the map against it’s schema. If you want to check the values in the output message then it is up to you to implement your own logic for this, and if you want to test variations in some of the elements in your input message then you really need to be creating and managing more copies of the input message which can quickly escalate into a management nightmare. This doesn’t leave one with the utmost confidence in their map when making changes to it, nor does it assure one that all the functional requirements are being catered for.
The BizTalk Map Testing Framework provides a solution to many of these problems. It allows you to run a map against an instance of an input message and it will compare the actual output of the map against a pre-prepared expected output message. This is referred to as a base test.
It also allows you make multiple additional tests with variations of elements in the input message based on the same file, and validates the output against a variation of the output message also based on the same file. These variations are catered for through collections (one for the input message and one for the output message) of xpath statements which contain the elements which need to be varied for that given test case. The test implementor would then create test cases, each test case containing different values which will be replaced in the input and output message based on the xpath statements.
The BizTalk Map Testing Framework also comes with overridable methods called OnTestExecuting and OnTestExecuted which allow you to perform your own custom validation before or after the maps have been executed (note that OnTestExecuted might be run after the map has been executed but it will still be run before the test cases themselves have been executed). This is made easy as the framework also includes some handy helper classes that let you extract values from either the input or output messages to validate them, or even to replace the values in them with a pre-prepared value. This is especially handy when the output message will contain some dynamic values such as a GUID or the current date time and you want to perform some custom validation on them before replacing them with a static value so that the static comparison against the output message still succeeds.
Let’s illustrate this with an example. Our input message contains an element called EventType and if it has a value of U then a value of Update will be mapped to an element called EventType in the output message, if the value in the input is D the value in the output is Delete, and if the element has a blank value or doesn’t exist in the input then a value of Unknown should be mapped to the output, if the input has any other value then the output element shouldn’t exist at all. We also have a repeating element in the input message called EventCode which can exist upto 4 times, and an EventCodes element in the output message which is a concatenation of all the source elements but will be suppressed if there are no EventCode elements in the source message. We also want an element called Id in the output message to contain a valid randomly generated GUID.
The map and the example instances which we are going to use in our test class are as below.
Before we start we want to ensure that the projects containing our BizTalk maps and schemas are set to allow for unit testing. Do this by opening the project properties, browsing to the deployment tab and enabling unit testing. Ensure you deploy the solution after doing this.
We now need to create a test project, add a reference to our projects containing the BizTalk maps and schemas (only necessary if we want to do schema validation), add a reference to the MapTestFramework.Common dll, and references to the Microsoft.BizTalk.TestTools and Microsoft.XLANGS.BaseTypes dlls.
Next up we add a test class (remember to decorate your class with the TestClass attribute) and inherit from the MappingFixture base class contained within the MapTestFramework.Common namespace. You’ll now want to override the CreateMap method and the SourcePathBase and ExpectedPathBase properties as below so that the test class knows which map, input file, and output file it is going to be using.
Now we want to implement our base test which will perform a static comparison of our output file to the actual file that was generated. The base test should look like below.
When you attempt to execute the base test you will notice that it fails since the value of the GUID is randomly generated and thus doesn’t match the GUID in our expected output file. In order to cater for this we would need to override the OnTestExecuted method as below such that it validates the GUID in the actual output file (we’ll throw in schema validation here too just for fun, note that the below implementation of schema validation has many limitations but I have shown this for the sake of brevity), and then replace the GUID with the one that has been set in the static expected output file. Note the usage of methods within the XmlHelper namespace which are used to read in elements from the output file and to replace them as well.
The base test should now execute and return a succesful result.
Now we need to implement a test which checks for the different values in the EventType element. Create a new test method and instantiate an object of type MapTestCases passing two arrays of strings into the constructor, the first array containing a list of xpath statements that we want to use to manipulate the source message before the test is executed, and the second array containing a list of xpath statements that are used to manipulate the expected output file before comparing it to the actual output file. You can obtain these xpath statements from the Visual Studio schema editor or through a tool such as DanSharp XMLViewer. The MapTestCases object instantiation should look like the below (notice that I have removed all the namespaces in the xpath statements to make the validation namespace agnostic, it is your choice whether you want to do this or not).
In the above screenshot the first TestCase prescribes that if the EventType in the input message is U then the EventType in the output message must be Update. The third TestCase prescribes that if there is no EventType element in the input message then the EventType in the output message must have a value of Unknown. The fifth TestCase prescribes that if the value of EventType in the input message is OtherValue then there should be no EventType element in the output message.
Lastly you will want to instruct the test method that it should now execute the map test against all the test cases that you have added to the collection as below. Each one of the TestCases will be executed when the test method is executed and only if all pass will the test method be considered succesful.
The test for the EventCodes is somewhat different because there are repeating elements in the input message. The trick here is that if you want your xpath collection to include multiple iterations of the same node and you are expecting to set some of the nodes to null, then you must specify the nodes in backwards order. This is just logical because if you have 4 nodes and you set the first node to null then when you try to access the 4th node it will not exist because it is now actually the 3rd node. Thus you must start by setting the last node to have a null value and then work your way backwards. I have implemented this test method as below.
Some other things to consider.
- I have not as yet been able to get the map test framework to work with a map in which the input message is an envelope schema containing an xsd:any node which links to an external xslt file. I have reverted to using Visual Studio map tests for this map.
- Executing the map tests will result in lots of files being created in the expected output file folder. You will probably want to add a TestCleanUp method to your test class which deletes all the unnecessary files.
- You will want to think of a smart folder pattern to store all your map files. This will make it a lot easier to manage all the input and output files.
- You’ll want to ensure that your BizTalk projects have unit testing disabled in your release build which you use to create deployments for your QA/Production environments. The problem is that whenever you deploy based on your release profile and go back to your debug profile you will get a build error when you try to build your solution since the unit testable BizTalk artifacts aren’t deployed. I work around this by creating an extra build profile which has unit testing enabled on my BizTalk projects but doesn’t build my unit testing projects, and everytime I have deployed a release build and want to go back to debug I will first do a deploy on the intermediate build profile and then go back to the debug profile.
- It is possible to have your map test case contain an xpath statement for a record/complex type element in your input or output message which contain multiple elements within it, and instead of just passing a string value into the individual test cases you can pass in the XML for all the contained elements (don’t include the XML tags for the record/complex type element itself).
I have uploaded a zipped version of my solution containing my BizTalk Server 2010 project and my map test project. Feel free to download and experiment with it.