mandag 27. april 2015

Testing a real world BizTalk integration with TransMock - Part 2

This post is the second one in the series "Testing real world BizTalk integration with TransMock".  It describes the creation of a test case and covers the flow of messages between the ERP and WMS systems that implement the goods reservation.
After all the wiring is performed  as explained in the first post, and the solution is deployed to the local BizTalk server instance, it is time to create our first test case. The process of creating a test case is divided in 2 steps - preparation and implementation. In the preparation step I will create the test class and prepare all the files that contain the different messages which will be sent to BizTalk server by the various test steps. The second step is covering the actual coding of the test. 

Preparation

First I start by adding a class called SOProcurementTests to the IntegrationTests project. There should be added references to the BizUnit.dll, BizUnit.Xaml.dll and TransMock.Integration.BizTalk.dll assemblies to that project. The latter one is located in the Ext folder under the TransMock installation folder.
After this I add the SOProcurementMockAddresses.cs class file to the IntegrationTests project. If you recall this file was generated by the Mockifier during deployment of the solution and should be located in the folder wheter the IntegrationTests project resides.
The test case I create here will be testing the happy path of execution and is therefore called NewSO_HappyPath. 

Now it is time to add to the test project the files that the test case will be sending to BizTalk server. From the flow diagram in the first post we can  identify that for the purpose of the goods reservation message flow we have the following messages that are sent from a system to BizTalk server:
  • SO created message - XML message sent from the ERP to BizTalk
  • Goods reservation response message - SOAP message sent from the WMS system to BizTalk
For the purpose of testing with TransMock we store these messages in corresponding files and then use those files from within the test. For this purpose I add a folder to the project called TestData and add 2 files to it that will contain each message correspondingly. These files are called respectively:
  • NewSO_HappyPath.xml - contains the XML for a new sales order 
  • WMSReservationResponse_HappyPath.xml - contains the SOAP XML with the warehouse reservation response
Note the naming convention here denoting that these files are related to testing a happy path scenario. This is a good practice for naming the message files as over time with introducing more tests it might become difficult to manage those. So give this a thought and come up with some naming scheme that suits your needs before there are too many files and test cases which might make your life miserable.

In this case the SO created message is a standard Dynamics AX entity XML for a sales order. A cut down version of the contents of this file, emphasizing on the important to the integration fields, is shown below:
NewSO.xml contents
….
<?xml version="1.0" encoding="utf-8" ?> 
<Envelope xmlns="http://schemas.microsoft.com/dynamics/2011
    01/documents/Message">
    <Header>
        <MessageId>{2002A291-8AA4-4405-BC10-383C30376F76}</MessageId>
        <Action>http://schemas.microsoft.com/dynamics/
            2008/01/services/SalesOrderService/insert</Action>
        <RequestMessageId>{13A5A1E4-D248-4361-B484-791AE121C952}
            </RequestMessageId> 
    </Header>
    <Body>
        <MessageParts xmlns="http://schemas.microsoft.com/dynamics/
            2011/01/documents/Message">
            <SalesOrder xmlns="http://schemas.microsoft.com/dynamics/
                2008/01/documents/SalesOrder">
                <DocPurpose>Original</DocPurpose>
                <SenderId>DAT</SenderId>
                <SalesTable class="entity">
                    <_DocumentHash>de321661b44419fc5052246e6274f726
                        </_DocumentHash>
                    ...
                    <CurrencyCode>EUR</CurrencyCode>
                    <CustAccount>4005</CustAccount>
                    <CustGroup>40</CustGroup>
                    <Deadline>2002-03-31</Deadline>
                    <DeliveryAddress>St. Hallvard vei 1 N-0244 
                        Oslo Norway</DeliveryAddress>
                    <DeliveryCity>Oslo</DeliveryCity>
                    <DeliveryCountryRegionId>NO
                        </DeliveryCountryRegionId
                    <DeliveryDate>2002-03-01</DeliveryDate>
                   ...
                    <DeliveryName>Office Supplies Inc.</DeliveryName>
                    ...
                    <DlvMode>UPS</DlvMode>
                   ...
                    <SalesGroup>CSG-OTH</SalesGroup>
                    <SalesId>00016_036</SalesId>
                    <SalesName>Office Supplies Inc.</SalesName>
                    <SalesPoolId>DEF</SalesPoolId>
                    <SalesResponsible>MPO</SalesResponsible>
                    <SalesStatus>Backorder</SalesStatus>
                    <SalesTaker>TJO</SalesTaker>
                    <SalesType>Sales</SalesType>
                    <SettleVoucher>None</SettleVoucher>
                    ...
                    <totalBalance>3300.00</totalBalance>
                    <TotalCashDiscount>33.00</TotalCashDiscount>
                    <TotalInvoice>3300.00</TotalInvoice>
                        <SalesLine class="entity">
                            ...
                            <Complete>No</Complete>
                            <ConfirmedDlv>2002-03-01</ConfirmedDlv>
                            <CurrencyCode>EUR</CurrencyCode>
                            <CustAccount>4005</CustAccount>
                            ...
                            <DeliveryType>None</DeliveryType>
                            <DeliveryZipCode>N-0244</DeliveryZipCode>
                            <InventDimId>00001_060</InventDimId>
                            <InventRefType>None</InventRefType>
                            <InventTransId>00065_059</InventTransId>
                            <ItemId>OL-1500</ItemId>
                          ...
                            <LineAmount>3300.00</LineAmount>
                            <LineNum>1.0000000000</LineNum>
                            <Name>Office Lamp 1500 2-tubes</Name>
                            <PalletTagging>No</PalletTagging>
                            <PriceUnit>1.00</PriceUnit>
                            <ProjCategoryId>Lamps</ProjCategoryId>
                            <QtyOrdered>200.00</QtyOrdered>
                            ...
                            <Reservation>None</Reservation>
                            ...
                            <SalesGroup>CSG-OTH</SalesGroup>
                            <SalesId>00016_036</SalesId>
                            <SalesPrice>16.50</SalesPrice>
                            <SalesQty>200.00</SalesQty>
                            <SalesStatus>Backorder</SalesStatus>
                            <SalesType>Sales</SalesType>
                            <SalesUnit>Pcs</SalesUnit>
                            <Scrap>No</Scrap>
                            <TaxAutogenerated>Yes</TaxAutogenerated>
                            <TaxItemGroup>full</TaxItemGroup>
                            ...
                        </SalesLine>
                    </SalesTable>
            </SalesOrder>
        </MessageParts>
    </Body>
</Envelope>

The WMS reservation response is from a custom web service exposed by the WMS system and is much smaller in size. It looks like this in its entirety:
WMSReservationResponse_HappyPath.xml
<s:Envelope xmlns:s="http://www.w3c.org/standards/SOAP-XML/2001">
 <s:Header>
 ....
 </s:Header>
 <s:Body>
  <wms:reserveGoodsResponse xmlns:wms="WMS Service namespace here">
  <operation>RSVNRL</operation>
  <respCode>100</respCode>
  <extRef>00016_036</extRef>
  <items>
   <item>
    <id>00001_060</id>
    <inventLocId></inventLocId>
    <resultCode>1</resultCode>
    <item>
  </items>
  </wms:reserveGoodsResponse>
 </s:Body>
</s:Envelope>

At last in order for the test case to be able to use these files there should be added the following attributes to the test method definition:

[DeploymentItem(@"TestData\NewSO_HappyPath.xml")]
[DeploymentItem(@"TestData\WMSReservationResponse_HappyPath.xml")]
public void NewSO_HappyPath()
{
}

You have to make sure that these files are always copied to the deployment folder used under test execution. The easiest way to do this is to set the Build action in the Properties window for each file to be Copy Always
With this all the preparation for the test case is done and we can carry out with the actual implementation.

Test implementation

The first thing I will do is to add a definition of a BizUnit test cse to the test method:
public void NewSO_HappyPath()
{
    TestCase testCase = new TestCase("NewSO_HappyPath");
    ...
Then I will add the first test step that will send the SO message to the ERP_SOCreateIn_MSMQ receive location and with this it will initiate the process. The major difference from a conventional BizUnit test case when using TransMock is that only 4 distinct test step types are used throughout the entire test. These test steps are making it possible to directly communicate with a corresponding receive location or send port which are executing the mock adapter. These steps are referred to as the Mock* steps and are defined in the TransMock.Integration.BizUnit namespace.
So, for the purpose of sending a one-way message to the ERP_SOCreateIn_MSMQ receive location I will use the MockSendStep. The code snippet below shows how it will be set up:

var erpSendNewSalesOrderStep = new MockSendStep()
     {
         Url = SalesOrderProcurementMockAddresses.ERP_SOCreateIn_MSMQ,
         Encoding = "UTF-8",
         RequestPath = "NewSO_SingleLine_HappyPath.xml"
     };

     testCase.ExecutionSteps.Add(erpSendNewSalesOrderStep);

Notice how the URL property of the step is set - this is where the helper class generated by the Mockifier comes in handy. In this particular case we are basically assigning the value of a property having the same name as the receive location to which the message should be sent. This is a very convenient way of setting the correct URL - for each mocked receive location and send port there is a corresponding get property in this helper class with the exact same name which returns the corresponding mock URL.
Another property that is required to be set on this step is the RequestPath - it must be set to the path of a file that contains the message that the step will be sending to BizTalk. In this particular case we set it to the NewSO_HappyPath.xml which contains the SO created message.
The Encoding property is also required and it should be set to the encoding in which the message shall be serialized and sent to the corresponding receive location.
The Timeout property is an optional one and is set to the number of seconds for which the operation would wait. In case the message is not sent for the defined timeout an exception is thrown by the step.

The next step is to define the test step that will act as the web service method for the reservation in the warehouse management system. Note that we will only emulate this web method alone and not spin any web service stub on the fly! This is fundamental in using TransMock that makes it super easy to test even the most complex scenarios. For this purpose we will utilize the MockRequestResponseStep which receives a request message from BizTalk and sends a synchronous response back. We will be verifying the received request and will be sending the expected response from a file. The code snippet below shows how this is done:
    ….
    SchemaDefinition wmsRequestSchema = new SchemaDefinition(){
        XmlSchemaNameSpace = "http://somewms.com/services/ManamgementService/v12",
        XmlSchemaPath = @"..\..\..\Schemas\WMS\WMS_ManagementService.xsd"
    };

    var wmsGoodsReservationRequestValidationStep = new XmlValidationStep();
            
    wmsGoodsReservationRequestValidationStep
      .XmlSchemas.Add(wmsRequestSchema);
            
    var xpathNumOfItems = new XPathDefinition(){
        XPath = "count(/*[local-name()='reserveGoods' and 
        namespace-uri()='http://somewms.com/services/ManamgementService/v12']/*[local-name()='items' and 
        namespace-uri()='']/*[local-name()='item' and namespace-uri()=''])",
        Value = "1"
    };

    wmsGoodsReservationRequestValidationStep
        .XPathValidations.Add(xpathNumOfItems);

    var xpathItemCommand = new XPathDefinition()
    {
         XPath = "/*[local-name()='reserveGoods' and 
             namespace-uri()='http://somewms.com/services/ManamgementService/v12']
             /*[local-name()='items' and namespace-uri()='']
             /*[local-name()='item' and namespace-uri()='' and position()='1']
             /*[local-name()='cmd' and namespace-uri()='']/text()",
         Value = "RSRV"
    };

    wmsGoodsReservationRequestValidationStep
        .XPathValidations.Add(xpathItemCommand);

    var wmsGoodsReservationRequestResponseStep = new MockRequestResponseStep()
    {
        Url = SalesOrderProcurementMockAddresses.WMS_ManagementService_WCF,
        Encoding = "UTF-8",
        ResponsePath = "WMS_ReservationResponse_SingleLine_HappyPath.xml",
        Timeout = 10
    };

    wmsGoodsReservationRequestResponseStep
        .SubSteps.Add(wmsGoodsReservationRequestValidationStep);
    
    testCase.ExecutionSteps.Add(wmsGoodsReservationRequestResponseStep);
The main difference with the first step that we created earlier is that here we set the URL property to a different value and that we set the ResponsePath property with the path to the file that contains the actual response message. 

In addition we configure and set an XmlValidatorStep that will execute several XPath validations against the request that was received. In particular these are:
1. The count of the nodes /reserveGoods/items/item is equal to 1
2. The value of the element /reserveGoods/items/item[1]/cmd is RSRV
Finally we set up the test step that mocks the reception of the reservation confirmation message by the ERP system. For this purpose we utilize the MockReceiveStep which implements a one way receive operation. Setting up this step is pretty straight forward and is shown in the snippet below:
….
var erpSalesOrderSchemaDefinition = new SchemaDefinition()
{
    XmlSchemaNameSpace = "http://schemas.microsoft.com/dynamics/2011/01/documents/Message",
    XmlSchemaPath = @"..\..\..\Schemas\ERP\MessageEnvelope.xsd"
};

var erpSOReservationConfirmValidationStep = new XmlValidationStep();

erpSOReservationConfirmValidationStep.XmlSchemas.Add(erpSalesOrderSchemaDefinition);

var xpathItemDimensionCount = new XPathDefinition()
{
    XPath = "count(/*[local-name()='Envelope' and 
        namespace-uri()='http://schemas.microsoft.com/dynamics/2011/01/documents/Message']
    /*[local-name()='Body' and 
        namespace-uri()='http://schemas.microsoft.com/dynamics/2011/01/documents/Message']
    /*[local-name()='MessageParts' and 
        namespace-uri()='http://schemas.microsoft.com/dynamics/2011/01/documents/Message']
    /*[local-name()='SalesOrder' and 
        namespace-uri()='http://schemas.microsoft.com/dynamics/2008/01/documents/SalesOrder']
    /*[local-name()='SalesTable' and 
        namespace-uri()='http://schemas.microsoft.com/dynamics/2008/01/documents/SalesOrder']
    /*[local-name()='SalesLine' and 
        namespace-uri()='http://schemas.microsoft.com/dynamics/2008/01/documents/SalesOrder']
    /*[local-name()='InventDim' and 
        namespace-uri()='http://schemas.microsoft.com/dynamics/2008/01/documents/SalesOrder'])",
    Value = "1"
};

erpSOReservationConfirmValidationStep
    .XPathValidations.Add(xpathItemDimensionCount);

var erpSOReservationConfirmStep = new MockReceiveStep()
{
    Url = SalesOrderProcurementMockAddresses.ERP_SOReservationConfirm_MSMQ,
    Encoding = "UTF-8",
    Timeout = 10
};

erpSOReservationConfirmStep.SubSteps
    .Add(erpSOReservationConfirmValidationStep);

testCase.ExecutionSteps.Add(erpSOReservationConfirmStep);
Again the important things to notice here is that first of all we do not have any sort of path related property to set here - the received message is simply consumed by the step and passed along any validation steps that might be configured, as is the case here. And of course the URL property is set for the corresponding send port.

The message that we receive in this step from BizTalk is a modified SO message with fewer fields carrying specific updated values indicating that the ordered goods have been reserved. The validation step is set to evaluate the following field:
        1. The count of /Envelope/Body/MessageParts/SalesOrder/SalesTable/SalesLine/InventDim is 1

And at the very end of the test method we add the 2 lines that will execute the entire test case

BizUnit.BizUnit testRunner = new BizUnit.BizUnit(testCase);
testRunner.RunTest();

In the next post you can read on how the shipment creation flow is tested. Continue to the next Post 3

fredag 9. januar 2015

Testing a real world BizTalk integration with TransMock - Part 1

TransMock is a neat little framework that allows you to easily test the functionality of a BizTalk Server integration on a developer box or a build server without the need of complex set ups of system test instances or respective stubs and mock-ups.

In this series of blog posts I will demonstrate how easy it is to test an existing integration with this framework.

Ovreview

The integration used as an example is based on a real life implementation of a SO and shipment procurement process. The whole process involves multiple interfaces implemented as separate discrete integrations, so for the sake of simplicity these are all merged together in a single application.

The process flow is as follows:
  1. An SO is registered in the ERP and a reservation message for the products is sent over a MSMQ queue to BizTalk
  2. The reservation is then sent further to the warehouse system over a web service call.
  3. The warehouse system supplies an synchronous response of the reservation to BizTalk over another web service call, which is then sent back to the ERP over another MSMQ queue
  4. The shipment for the SO is prepared in the ERP and when it is finished a shipment request is sent over FTP to an external logistics provider. EDI message is used for this purpose
  5. The logistics provider sends a confirmation that they can handle the shipment with another EDI message over FTP. This message is transformed to the corresponding ERP format and sent back to the ERP over a MSMQ.
  6. When the time for shipping the goods has come a packing request is sent from the ERP over an MSMQ and BizTalk sends it further to the warehouse system over another web service call.
  7. Once the goods are packed and prepared to be shipped a ready to ship message is sent back from  the warehouse system over a web service call to BizTalk and it sends it further to the ERP over MSMQ.
  8. Once the goods are picked and shipped by the shipping provider an  ASN - advanced shipment notice message is sent over FTP from the shipping provider to the ERP and the customers which effectively finlizes the process.

As you can see there are a number of different message formats and a number of different transport protocols used in this integration solution. This is the norm for a typical BizTalk server based solution.

So, before carrying out with the instructions on how to test this integration with the help of TransMock few clarifications should be made:
  • First of all, the integration implementation uses BTDF for managing the packaging and deployment to the different environments.
  • Secondly, the message formats and details are much more simplified for the sake of clarity. Remember that the purpose of the post is not to go in detail on how to define a schema for a message to a warehousing system or a logistics provider, but instead to emphasize on how to test an integration without the need of implementing some complex mockups of those systems, or even installing and configuring a specific test instance of a specific system for that purpose.
  • And lastly, it is assumed that you are familiar with BizUnit based tests and that you have installed both BizUnit and the TransMock framework on your dev box.

Alright, time for fun.

Modifying the bindings file

The first thing that you need to do is to identify the receive locations and send ports that are involved in communicating with the system interfaces in question. In our bindings file this is what we find

System name
Receive location name
Send port name
Purpose
Dynamix AX
ERP_SOCreatedIn_MSMQ

SO created
Dynamix AX
ERP_PackageNotificationIn_MSMQ

 Package and ship
Dynamics AX

ERP_SOReservationOut_MSMQ
Reservation response
Dynamics AX

ERP_ReadyToShipOut_MSMQ
Ready to ship response,
Dynamics AX

ERP_ShipmentConfirmOut_MSMQ
Tender response
Dynamics AX

ERP_ASNOut_MSMQ
Shipment ASN
Warehouse

WMS_InventoryManagementOut_WCF
Reservation, Package and ship
Warehouse
WMS_InventoryManagementIn_WCF

Ready to ship response
Shipping Partner

SHP_ShipmentRequestOut_EDI_SFTP
Transportation
tender request
Shipping Partner

SHP_ShipmentUpdateOut_EDI_SFTP
Shipment update request
Shipping Partner
SHP_TendertResponseIn_EDI_SFTP

Shipment response
Shipping Partner
SHP_ShipmentUpdateIn_EDI_SFTP

Shipment update response
Shipping Partner
SHP_ASN_FTP

Shipment ASN

In total we have 6 receive locations and 7 send ports. Please note that both the receive locations and send ports are being used to respectively send and receive different message types (* might be not correct according to the standard usage of these artifacts). This makes the testing even more challenging.

Once we have identified these the next step is to mark them as eligible for mocking. This is done by adding the Mock tag in a comment right under the respective ReceiveLocation and SendPort elements in the bindings file. In the snippet below is shown an example of that:
...
<SendPort name="WMS_InventoryManagementOut_WCF">
<!-- <Mock />-->
...
</SendPort >
...
<ReceiveLocation name="ERP_SOReservationOut_MSMQ">
<!-- <Mock /> -->
...
</ReceiveLocation >
...

This is the most simple way of preparing the bindings file for mocking by TransMock. By applying the Mock tag the Mockifier replaces the actual adapter configuration in each and every receive location and send port that is marked for mocking with a predefined configuration of the mock adapter. In addition it replaces the address property with a mock URL. By default the mock URL is containing the name of the corresponding receive location or send port by following the pattern mock://localhost/[Receive location name | Send port name] . This might sometimes be a bit difficult to read and not that nice looking. I would therefore like to demonstrate one feature of the framework which allows to have customized mock URLs which are easier to understand. For this purpose I would simply set value for the attribute EndpointName in each appearance of the Mock tag, as shown in the snippet below:

…
<SendPort Name="WMS_InventoryManagementOut_WCF" …. >
<!-- <Mock EndpointName="WMS_InventoryManagementOut" /> -->
…
</SendPort>
…
<ReceiveLocation Name="ERP_SOProcurementOut_MSMQ">
<!-- <Mock EndpointName="ERP_SOProcurementOut" /> -->
…
</ReceiveLocation>
…

The resulting URLs would then be as follows:
mock://localhost/WMS_InventoryManagementOut for the WMS_InventoryManagementOut_WCF send port, and
mock://localhost/ERP_SOProcurementOut for the ERP_SOProcurementOut_MSMQ receive location

As you can see I have simply removed the name of the original transport in each of the EndpointName values which makes the URLs much more understandable and minimizing the confusion it might create when the original transport name is there.

Please note that this is not required to have the TransMock working correctly. It is a technique for modifying the default mock URLs which might be handy in many situations.

With this we are done with preparing the bindings file for being used by the TransMock framework.

Modifying the .btdfproj file

It is now time to modify the btdfproj file so that the Mockifier of the TransMock framework is invoked properly. There are couple of things that need to be done to make this happen. First of all there should be added a reference to a targets file that contains the TransMock build targets. This is done by adding the following code at the very end of the .btdfproj just before the closing Project tag:

<Import Project="[Path to the installation folder of TransMock]\Ext\TransMock.targets" />

By default TransMock is installed under the folder "C:\Program files (x86)\TransMock" on a 64-bit machine and the Import tag would look like this:

<Import Project="C:\Program files (x86)\TransMock\Ext\TransMock.targets" />

Update: Since TransMock v 1.3 the only distribution channel for the framework is NuGet. Hence the Import tag will look as follows:

<Import Project="..\packages\TransMock.Framework.[version in format X.Y.Z]\BTDF\TransMock.targets" />

There are few TransMock specific project properties that should be set. These are:
  • BTSVersion - optional, should be set to 2010 if BizTalk 2010 solution is being tested. No need to be set  when 2013/R2 solution is being tested.
  • TransMockAddressClassDir - optional, this should be se to the path where the test project containing the TransMock tests resides. Relative paths is the preferred format. If not set the helper class generated by the Mockifier would be saved in the same directory where the btdfproj file resides.
  • TransMockHomeDir - optional. This property should only be set in the case when TransMock is installed in a location different than the default one. It should contain this custom path then.

In our case the BizTalk version is 2013 R2, the test project is called IntegrationTests and resides in a subfolder with the same name under the solution root and TransMock is installed at the default location. Hence only the value of the TransMockAddressClassDir  property will be set:

<TransMockAddressClassDir>..\IntegrationTests</TransMockAddressClassDir>

With this the btdfproj file is prepared for running the Mockifier as part of the BTDF deploy process.

At this point you should build and deploy the solution which will result in importing the bindings with all the receive locations and send ports having the mock adapter as their transport and their addresses having mock URLs. In addition a file called SOProcurementMockAddresses.cs has been generated in the IntegrationTests folder.

Continue to Part 2