Introduction
In May 2011, a new version of BizUnit was released. This article hopes to demonstrate how to use BizUnit 4.0 to unit test a simple orchestration in BizTalk 2010. This will be the first of a number of BizUnit and BizTalk 2010 related articles.
This article covers a very simple scenario that demonstrates a small part of the abilities of BizUnit. Unit testing an orchestration.
Background
Unit testing is invaluable in software development and it is something that is tricky to do with BizTalk but at the same time it is essential that it happens. BizUnit is a brilliant tool and should be used routinely by BizTalk developers to ensure the quality of the code that they are producing.
BizUnit takes a black box approach to testing BizTalk applications. The test in this article does the following:
- Copies a sample file and drops it into a BizTalk receive location
- Checks that the correct number of files arrive in the destination location
- Validates those files against the XSD
- Check the content of the file
Required Download
The latest version of BizUnit (Version 4 for this article) should be downloaded from CodePlex.
Required Software
The following software is required to run the sample code:
- Visual Studio 2010
- Microsoft BizTalk Server 2010
Sample BizTalk Project
Before we can do any testing, we need to create a test project. To do that, we need to define three schemas, two maps and an orchestration. This is a very very simple project but should be enough to get going.
Now, as everyone knows, in the TDD world we would write the tests before the code and then make the tests pass. This cannot really be done in BizTalk in the purest form. You will need the input and output schemas at the very least.
Remember to Strong Name the BizTalk solution or you will be unable to deploy it.
Schemas
The schemas we are going to define are an input schema to represent a customer order and then two output schemas which are a very simple order summary and a pick list. They are simple schemas and not exactly realistic but they serve the purpose of the article.
Source Schema
The source schema represents a multiline customer order. A single order can have multiple lines, each with a quantity and a price. It is a simple schema but will serve the purposes of this article.
The sample file used for the tests is:
<ns0:CustomerOrder xmlns:ns0="http://Timewave.BizTalkUnit.Sample.SourceSchema">
<Forename>Tycho</Forename>
<Surname>Lien</Surname>
<CustomerId>10000001</CustomerId>
<OrderTotal>1000.00</OrderTotal>
<PaymentStatus>Paid</PaymentStatus>
<OrderLines>
<Line>
<ItemId>1234</ItemId>
<ItemQty>1</ItemQty>
<PriceAtOrder>399.99</PriceAtOrder>
</Line>
<Line>
<ItemId>2345</ItemId>
<ItemQty>1</ItemQty>
<PriceAtOrder>100.01</PriceAtOrder>
</Line>
<Line>
<ItemId>3456</ItemId>
<ItemQty>1</ItemQty>
<PriceAtOrder>500.00</PriceAtOrder>
</Line>
</OrderLines>
</ns0:CustomerOrder>
Pick List Schema
The pick list schema will be a list of the items contained in the customer order (source schema). It isn't used as part of the test in this article.
Summary Schema
The summary schema will contain the details of the customer and a count of the number of items ordered.
Maps
We are going to create two maps. These maps will transform the source message into the Summary Message and the PickList Message.
Summary Map
The summary map takes the values from the customer order (source schema) and transforms them to the summary schema. Not all of the values from the source schema are used. The Forename
and Surname
elements are concatenated using a string
concatenate functoid. A Record Count functoid is used to get a count of the number of items.
Pick List Map
The pick list map simply transforms the customer order (source schema) document to the pick list schema. The CustomerId
used and a looping functoid makes sure that an Item
is created for each order line.
Orchestration
The orchestration is, once again, relatively simple. A document that conforms to the source schema is picked up from a file location and pulled into the orchestration. The file is transformed to the summary schema and then a send port places it into the relevant file location. The same source document is then transformed to the pick list schema and then placed into the pick list file location.
ProcessOrder.odx
Simple, but all we need for the purposes of this test.
BizTalk Deployment
I have assumed that you know how to build a BizTalk solution and deploy it to BizTalk and as such I am not going to cover that here.
Just make sure that the receive and send locations in BizTalk match those on the test project that follows.
Test Project
Once the BizTalk project is up and running and deployed to BizTalk, then it is time to create the test project. In the real world, you should have done this just after having created the BizTalk project. The tests would be being created after having built the Schemas and before doing anything else.
So, add a test project to the solution.
Next, we need to add the BizUnit references to the test project. You will need to browse to these, they will be at C:\Program Files\BizUnit\BizUnit 4.0\Bins, assuming a standard installation.
You need to add references to:
BizUnit
BizUnit.TestSteps
BizUnit.TestSteps.BizTalk
Once that is done, we are ready to start creating our test method. Now, I am using resharper which nicely adds "using
" statements for me. Assuming that you are not, then make sure you have using
statements for the references that we added previously.
The code that follows is the complete code for the test method. I will break down each part of the code and explain it afterwards. One important point to note is that none of the code takes any action until the final RunTest()
command. One of the features of BizUnit is that tests can be scripted in XAML (XML in previous versions). The next article will show this same example using the XAML script.
[TestMethod]
public void Total_Items_Is_3()
{
var totalItemsTest = new TestCase { Name = "Total Items is 3" };
DeleteStep deleteStep = new DeleteStep();
var filePathsToDelete = new Collection<string> {@"..\..\..\Pickup\Out\Summary\*.xml"};
deleteStep.FilePathsToDelete = filePathsToDelete;
totalItemsTest.SetupSteps.Add(deleteStep);
var testStep = new CreateStep();
testStep.CreationPath = @"..\..\..\Pickup\In\InboundCorrect.xml";
var dataLoader = new FileDataLoader();
dataLoader.FilePath = @"..\..\..\TestData\InboundCorrect.xml";
testStep.DataSource = dataLoader;
totalItemsTest.ExecutionSteps.Add(testStep);
var validatingFileReadStep = new FileReadMultipleStep
{
DirectoryPath = @"..\..\..\Pickup\Out\Summary",
SearchPattern = "*.xml",
ExpectedNumberOfFiles = 1,
Timeout = 3000,
DeleteFiles = true
};
var validation = new XmlValidationStep();
var schemaSummary = new SchemaDefinition
{
XmlSchemaPath =
@"..\..\..\Timewave.BizUnit.Sample\Schemas\SummarySchema.xsd",
XmlSchemaNameSpace =
"http://Timewave.BizTalkUnit.Sample.DestinationSchema"
};
validation.XmlSchemas.Add(schemaSummary);
var xpathProductId = new XPathDefinition
{
Description = "ItemsOrdered",
XPath =
"/*[local-name()='CustomerSummary' and
namespace-uri()='http://Timewave.BizTalkUnit.Sample.DestinationSchema']/
*[local-name()='ItemsOrdered' and namespace-uri()='']",
Value = "3"
};
validation.XPathValidations.Add(xpathProductId);
validatingFileReadStep.SubSteps.Add(validation);
totalItemsTest.ExecutionSteps.Add(validatingFileReadStep);
var bizUnit = new BizUnit.BizUnit(totalItemsTest);
bizUnit.RunTest();
}
The first part of the code might prove controversial. Personally, I think it is a style thing rather than a big issue. The first thing I do in each of my tests is make sure that the location I am going to be checking for the result is empty. Some articles on BizUnit suggest that this should be the last step, leave the system how it started, which I agree with, but I like to assume the worst, that a previous test has left a file in place which will give me a false positive, so I empty the location at the start. It is up to you if you do it at the start, end or both!
[TestMethod]
public void Total_Items_Is_3()
{
var totalItemsTest = new TestCase { Name = "Total Items is 3" };
DeleteStep deleteStep = new DeleteStep();
var filePathsToDelete = new Collection<string> {@"..\..\..\Pickup\Out\Summary\*.xml"};
deleteStep.FilePathsToDelete = filePathsToDelete;
totalItemsTest.SetupSteps.Add(deleteStep);
This is all reasonably straight forward, one thing to note. You cannot do:
deleteStep.FilePathsToDelete.Add(filename);
The reason being the BizUnit 4.0 (currently) does not initialise the collection in the class constructor so it causes an "Object reference not set
" error.
totalItemsTest.SetupSteps.Add(deleteStep)
will add the deletion step to the steps that will take place when the test is executed.
It is possible to replace totalItemsTest.SetupSteps.Add(deleteStep)
with deleteStep.Execute(new Context())
. This will cause the deletion step to happen immediately. However, if the test is serialised to XAML, then the step will not be included.
The next task is to create the test step itself.
var testStep = new CreateStep();
testStep.CreationPath = @"..\..\..\Pickup\In\InboundCorrect.xml";
var dataLoader = new FileDataLoader();
dataLoader.FilePath = @"..\..\..\TestData\InboundCorrect.xml";
testStep.DataSource = dataLoader;
totalItemsTest.ExecutionSteps.Add(testStep);
The CreationPath
is the path (directory and filename) that BizTalk should be watching with a receive port. This is where the file will arrive to be picked up by BizTalk. It is NOT the path that BizTalk will create the response.
The FilePath
is where BizUnit will go and get the sample from. In a real solution, this directory will contain all of your test documents.
Next, we create the steps that will take place after the test has fired. We are going to carry out 3 different checks. We are going to:
- Check that a single document has been produced
- Check that the document is valid against the XSD
- Check that a calculated value in the document is correct
To check that a file has been created, we create a FileReadMultipleStep
. This will go and check a particular directory for files that meets a certain pattern. I have used *.xml but it could easily be PO*.xml if your port uses a file name to distinguish output files.
var validatingFileReadStep = new FileReadMultipleStep
{
DirectoryPath = @"..\..\..\Pickup\Out\Summary",
SearchPattern = "*.xml",
ExpectedNumberOfFiles = 1,
Timeout = 3000,
DeleteFiles = true
};
So, the values here are reasonably obvious, we have the directory to check, the pattern of the file we are looking for, the number of files expected and the timeout. We also have a flag that tells BizUnit if it should delete the file(s) when it is done.
The Timeout
is important. If you have a complex orchestration that can take some time to run, then you will need to set this timeout. Your test has absolutely no knowledge of what is creating the file that it is looking for. It cannot check to see if BizTalk is done or if there is a delay on server or if a web service is spinning up. You need to use your judgement to set this to a realistic timeout. In my case, 3 seconds (3000 milliseconds) was sufficient.
DeleteFiles
will make this step delete the file once it has finished with it. It is good to tidy up after yourself! Remove this if you want to see the file that is produced.
Next we check that the XML is valid.
var validation = new XmlValidationStep();
var schemaSummary = new SchemaDefinition
{
XmlSchemaPath =
@"..\..\..\Timewave.BizUnit.Sample\Schemas\SummarySchema.xsd",
XmlSchemaNameSpace =
"http://Timewave.BizTalkUnit.Sample.DestinationSchema"
};
validation.XmlSchemas.Add(schemaSummary);
Again, this is straight forward. Make sure that you get the XmlSchemaNameSpace
correct as this will ensure a failure if it is wrong.
Next we create an XPath
Validation. This will go and check that the output document has a value in it as expected.
var xpathProductId = new XPathDefinition
{
Description = "ItemsOrdered",
XPath =
"/*[local-name()='CustomerSummary' and namespace-uri()=
'http://Timewave.BizTalkUnit.Sample.DestinationSchema']/
*[local-name()='ItemsOrdered' and namespace-uri()='']",
Value = "3"
};
validation.XPathValidations.Add(xpathProductId);
The three parameters used to construct the XPathDefinition
are a Description, the XPath for the element you are checking (which is copy and pasted from the XSD in the BizTalk XSD editor) and the value that you are expecting.
The final part of the code adds the validations we have just created to the test and then runs the test.
validatingFileReadStep.SubSteps.Add(validation);
totalItemsTest.ExecutionSteps.Add(validatingFileReadStep);
var bizUnit = new BizUnit.BizUnit(totalItemsTest);
bizUnit.RunTest();
Using the Code
To run the tests, you will need to deploy your solution to BizTalk and configure the ports.
Then, right click on the test and select run test. Once the test has run, you will either see "Passed" or "Failed". If it says failed, then right click on the result and select "View Test Results Details". You can then use the details to work out what the issue is. Please remember the points in the next section.
If Your Tests Fail
Unlike usual unit testing, a BizUnit test may fail for reasons other than the code being broken. Please remember the following when investigating failed tests. The test in the sample could fail for 3 different reasons. These are:
- No file in the specified directory
- Too many files in the specified directory (If this happens, then the delete is failing)
- XPath match failure
Now, under normal circumstances, you would dive into the code to find the bug that has been introduced. Before you do that, double check the following:
- The BizTalk application is running
- Your receive adapters are enabled
- Your send port is enabled
- The Orchestration is enlisted
- The Host instance is running
- The timeout in the code is long enough if you have a complex process
Next Steps
The next article will show how to do the exact same test as documented here, but with the tests defined in XAML rather than code.
History
- Initial version - 2011-08-20