Viser innlegg med etiketten BizTalk testing. Vis alle innlegg
Viser innlegg med etiketten BizTalk testing. Vis alle innlegg

tirsdag 8. oktober 2019

Introducing the new programming model of TransMock

Little history


Since its very early days TransMock relied explicitly on the very well established programming model of BizUnit. This was mainly due to the fact that the few developers out there who had grasped the paradigm of automating the testing of their integrations  in a fully fledged ALM/DevOps pipeline were already very familiar with this model. So for them taking into usage TransMock which was bringing complete isolation from external dependencies and thus greater flexibility was a no brainer.
But with the years while introducing new capabilities to TransMock this programming model started to show certain weaknesses. Particularly 3 such weaknesses that top the chart:
  • The execution model of BizUnit - all the steps are added to a list of steps and then the test case execution is performed in the context of the Run() method. This imposes big challenges especially when it comes to the inability to debug the tests.
  • The validation model of BizUnit - based on introducing validation step classes which encapsulate the specific validation logic. This was partially addressed by introducing the LambdaValidationStep class in TransMock 1.3 which allows the programmer to perform in-place validation, without this complex OO approach by simply defining a validation method callback that will be invoked at the desired point of test case execution
  • The code size of the tests - due to the way the steps were defined and validation steps added the code for a bit more complicated scenario is quickly blowing out of manageable proportions.
All this has lead to the creation of a completely new programming model that addresses many of the shortcomings of BizUnit. It is available from v1.5 of the TransMock framework and will be the main interface to it going forward.


Fundamental idea


The fundamental idea behind the new programming model is about emphasizing the message exchange between the test harness and the tested service in a neat way while giving the entire control of the test execution in the hands of the developer. And all this backed by a more modern syntax following a well known approach for creating object mocks from other well-established mocking frameworks out there.

On a very hi level the new programming model can be described as 2 main objects that interact with each other through exchanging messages of type MockMessage. 1 object represents the mocked endpoints of the actual service/integration that is being tested and the other is a messaging client that can only send and receive messages to/from the mocked endpoints in the first object. Verification of received messages from the service/integration is performed in place where the messages are actually received. This is a fundamental difference from how verification was performed in BizUnit.

The rest of the components in TransMock are intact - the mock adatper is still used to enable communication with the test cases and the mockifier is still used the same way to mockify the bindings and to produce the mocked addresses helper class. One slight difference is that the mocked addresses helper class that the mockifier generates is now strong typed, meaning that there is performed a design time type check based on the corresponding endpoints' 
messaging patterns in the message exchange methods of the messaging client object.


Syntax



For the new programming model it has been deliberately chosen to utilize fluid-like syntax and to be based on the principles of functional programming. This allows the developers to really focus on the logic of the tests in place of where they author those.

The two main classes in the new programming model are called respectively EndpointsMock<TAddresses> and TestMessagingClient<TAddresses>. The EndpointsMock class represents the set of mocked endpoints of a service/integration that will be tested. The TAddresses is a class of type EndpointAddress. The generated by the mockifier *MockAddresses helper class now inherits from this class bringing whole lot new capabilities for type checks. The EndpointsMock class defines a set of Setup methods depending on the type of the message exchange pattern implemented by a given endpoint. This is exactly as with classical mocking frameworks - they allow you to set up some mocked behaviour of methods, properties and other class members.  

The available setup methods in the EndpointsMock class are:

  • SetupSend - sets up a one-way send endpoint
  • SetupReceive - sets up a one-way receive endpoint
  • SetupSendRequestAndReceiveResponse- sets up a two-way send endpoint, one that is sending a request and receiving a response
  • SetupReceiveRequestAndSendResponse- sets up a two way receive endpoint, one that is receiving a request and sending a response
Common to all these methods is that they all operate against an instance of the defined TAddresses type through lambda expressions. This allows for design time type checks against the type of the endpoint which ensures that one can never setup a receive endpoint mock against a send endpoint or vice verse! This is very big improvement as it was totally possible to do such setup with the old, BizUnit based programming model, which was one of the main challenges for new beginners with TransMock.

Quick demo on creating instance of the EndpointsMock class:

///
/// Assumes The MyService_MockAddresses has a single one-way receive and single
/// one-way send endpoints
  var serviceMock = new EndpointsMock<MyService_MockAddresses>();
  serviceMock.SetupReceive(ep => ep.ReceivePO_FILE)
      .SetupSend(ep => ep.SendWMSRequest_MQSeries);
///

The TestMessagingClient<TAddresses> is responsible for sending and receiving messages to/from the mocked endpoints. It cannot be instantiated directly but only through a factory method on the instance of EndpointsMock class. This way it is ensured that the client will only ever exchange messages with the intended service/integration represented by the endpoints mock instance.

The TestMessagingClient class has the following methods that allow for exchanging messages with the mocked service/integration:
  • Send - sends a message to a receive mocked endpoint
  • Receive - receives a message from a send mocked endpoint
  • SendRequestAndReceiveResponse - sends a request and waits for receiving a response from the 2-way receive mocked endpoint
  • ReceiveRequestAndSendResponse - receives a request and sends a response to a 2-way send mocked endpoint
Here is a quick demo on how an instance of the TestMessagingClient is created and utilized:
///
/// Continuing from the example above
///

var testClient = serviceMock.CreateMessagingClient();
testClient.Send(rp => rp.ReceivePO_FILE, "TestPO.xml")
    .Receive(sp => sp.SendWMSRequest_MQSeries);

///

The above 3 lines of code efficiently test the entire service from end to end! One can chain the methods as in any other modern fluid syntax framework and the execution follows this very same path. No Run methods, no hidden magic!
In addition to the 4 Send&Receive methods the TestMessagingClient class defines a method called InParallel() which allows for executing a set of defined Send/Receive methods from the same test messaging client instance in a parallel manner. This comes very handy in situations where the tested service/integration does some complex parallel processing.


Authoring tests with the new programming model


Enough with the theory, time for a demonstration of how to utilize the new programming model. For the sake of simplicity we will use a very basic case:

  1. A two-way WCF-WebHttp receive location receives a call from a mobile app for an order placement
  2. The service invokes an order validation WCF-BasicHttp service on an order system
  3. After successfull validation the order is placed on an MSMQ queue and a response to the calling app is supplied simultaniously.
Few assumptions about the setup, as in line with previous examples:
  • Application is named MobileOrderPlacement
  • BTDF is used for deployment and the reader is familiar of how to set it up to work with TransMock targets for generating the *MockAddresses class
  • Bindings are prepared for mocking of the endpoints
  • The endpoints are named as follows:
    • ReceiveRestOrder_WCFWebHttp - the WebHttp 2-way receive location exponsing the REST service for order reception
    • SendValidateOrder_WCFBasicHttp - the 2-way send port for invoking the order validation web service
    • SendOrderRequest_MSMQ - the 1-way send port for placing the order request on a queue
The mockifier produces a class called MobileOrderPlacementMockAddresses which has the following properties:
  • ReceiveRestOrder_WCFWebHttp of type TwoWayReceiveAddress
  • SendValidateOrder_WCFBasicHttp of type TwoWaySendAddress
  • SendOrderRequest_MSMQ of type OneWaySendAddress
Important prerequisite for producing the correct variant of the *MockAddresses helper class is to set the following property in your *.btdfproj file with the given value:

///
...
<TransMockProgModel>New</TransMockProgModel>
...
///

This was introduced in order to maintain backward compatibility when generating the *MockAddresses class as the new style of this class is considered a breaking change. The default behaviour of the mockifier when invoked from BTDF is to generate a *MockAddresses class with string properties only, which is referred as Legacy.

First we create an instance of the EndpointsMock class and setup the mocked endpoints:

///
var serviceMock = new EndpointsMock<MyService_MockAddresses>();
serviceMock.SetupReceiveRequestAndSendResponse(ep => ep.ReceiveRestOrder_WCFWebHttp )
      .SetupSendRequestAndReceiveResponse(ep => ep.SendValidateOrder_WCFBasicHttp )
      .SetupSend(ep => ep.SendOrderRequest_MSMQ)
///


Then we define the actual test flow execution through the instance of the TestMessagingClient. Here it is important to note the usage of the InParallel() method of this class. It is required in this case as we have a synchronous request-response operation that initiates the service and waits for response while the rest of the flow is being executed.


///
var testClient = serviceMock.CreateMessagingClient();
testClient.InParallel(
   (tc) => tc.ReceiveRequestAndSendResponse(
        sp => sp.SendValidateOrder_WCFBasicHttp,
        responseSelector: rs => new StaticFileResponseSelector()
            {
                FilePath = "OrderApprovedResponse.xml"
            },
        requestValidator: rv => VerifyIncomingOrder(rv)),
        tc.Receive(sp => sp.SendOrderRequest_MSMQ,
            requestValidator: rv => VerifyOrderRequest(rv)
    )
)
.SendRequestAndReceiveResponse(rp => rp.ReceiveRestOrder_WCFWebHttp,
    "ValidOrderRequest.json",
    responseValidator: rv => VerifyOrderResponse(rv))
.VerifyParallel();
///

Note the usage of the helper class StaticFileResponseSelector() in the place where assigning a response for the order validation service. This class does what its name suggests - selects a response from a static file.

Note as well the last method invoked in the chain - VerifyParallel(). This method is required when performing operations in parallel in order to ensure that any operation that was started parallely either completes or fails and the exception is re-thrown in the main execution thread.

And finally the verification methods are defined as follows:


///
private bool VerifyIncomingOrder(ValidatableMessageReception v)
{
    Assert.IsTrue(v.Message.Body.Length > 0, "The incoming order reqeust message is empty");

    var xDoc = XDocument.Load(v.Message.BodyStream);
    Assert.IsTrue(
        xDoc.Root.Name.LocalName == "ValidateOrderRequest",
       "The contents of the order validation request is not as expected");
    
    return true;
}

private bool VerifyOrderRequest(indexedMessageReception v)
{
    Assert.IsTrue(v.Message.Body.Length > 0, "The request message is empty");

    var xDoc = XDocument.Load(v.Message.BodyStream);
    Assert.IsTrue(
        xDoc.Root.Name.LocalName == "OrderRequest",
       "The contents of the order validation request is not as expected");

    return true;
}

private bool VerifyOrderResponse(IndexedMessageReception v)
{
    Assert.IsTrue(v.Message.Body.Length > 0, "The order response message is empty");

    var orderResponse = JsonConverter
        .DeserializeObject<OrderResponse<(v.Message.Body);
    // OrderResponse is a pre-defined entity type corresponding to 
    // the OrderResponse JSON message
    
    Assert.IsTrue(
        orderResponse.Approved,
       "The order response was not as expected!");
    
    return true;
}
///

This is all about a test created with the new TransMock programming model! Compact and neat syntax empowering you to both control the flow of execution and verify the outcome of each and every message reception from the tested service/integration right there and then! And if you get stuck somewhere you simply put a breakpoint and debug the test to really see what is going on and why it keeps failing. Something that is still very difficult to do with BizUnit based tests.

fredag 11. mars 2016

Testing a real world BizTalk integration with TransMock - Part 3

In the previous post of the series "Testing a real world BizTalk integration with TransMock" I described how to utilize the TransMock framework for testing the goods reservation sub-process of the SO procurement process implementation. 
In this post I will continue with a new test that will exercise the happy path of the subflow for shipment creation . The test case is therefore called CreateShipment_HappyPath.

A quick refresher of the functional flow for this sub-process:
  • Once a shipment is created and confirmed in the ERP a shipment request message is generated and sent to the BizTalk server
  • The integration processes this message and produces as a result an EDI X12 204-Transportation tender request message which is sent to an external shipping partner
  • The shipping partner then response with an EDI X12 990-Tender response message which contains the response to whether the shipment can be picked and when.
  • This response is processed by the integration and as a result of this a shipment confirmation message is sent to the ERP.

The process of creating the test case is again divided in 2 stages - preparation and implementation of the test case.

Preparation
In this stage I will again prepare the files that the test case will be sending to BizTalk. From the flow diagram in the first post we can identify the following messages that are being sent to Biztalk server:
  • Shipment creation request - sent from the ERP to initiate the shipment management process. This is a an XML message representing a customized Shipment entity from Dynamics AX.
  • Shipment confirmation - sent from the external shipment provider as a response to a shipment request. This is a standard EDI X12 990 - Tender response message, which is a flat file.
The messages will reside in the following files:
  • NewShipment_SingleLine_HappyPath.xml - shipment creation request containing a single line item
  • NewTenderResponse_SingleLine_HappyPath.edi - the tender response message.
The first message is based on the Shipment entity XML in Dynamics and contains purely data related to the actual shipment. The below snippet shows how it looks like:
... 
ShipmentCreateRequest_HappyPath.xml
...
<?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/ShipmentService/insert</Action>
    <RequestMessageId>{13A5A1E4-D248-4361-B484-791AE121C952}</RequestMessageId>
  </Header>
  <Body>
    <MessageParts xmlns="http://schemas.microsoft.com/dynamics/2011/01/documents/Message">
      <Shipment xmlns="http://schemas.microsoft.com/dynamics/2008/01/documents/Shipment">
        <DocPurpose>Original</DocPurpose>
        <SenderId>ERP</SenderId>
        <ShipmentTable class="entity">
          <_DocumentHash>de321661b44419fc5052246e6274f726</_DocumentHash>
          <Deadline>2012-12-31</Deadline>
          <DeliveryAddress>Industriveien 133 N-0944 Oslo Norway</DeliveryAddress>
          <DeliveryCity>Oslo</DeliveryCity>
          <DeliveryCountryRegionId>NO</DeliveryCountryRegionId>
          <DeliveryDate>2015-12-31</DeliveryDate>
          <DeliveryDateControlType>None</DeliveryDateControlType>
          <DeliveryName>Office Supplies Inc.</DeliveryName>
          <DeliveryStreet>Industriveien  133</DeliveryStreet>
          <DeliveryZipCode>N-0944</DeliveryZipCode>
          <DlvMode>UPS</DlvMode>
          <RecId>5637144586</RecId>
          <RecVersion>1</RecVersion>
          <Reservation>None</Reservation>
          <ReturnReplacementCreated>No</ReturnReplacementCreated>
          <ReturnStatus>None</ReturnStatus>
          <ShipCarrierBlindShipment>No</ShipCarrierBlindShipment>
          <ShipCarrierDlvType>Misc</ShipCarrierDlvType>
          <ShippingDateRequested>2015-03-01</ShippingDateRequested>
          <SalesOrderTable class="entity">
            <SalesGroup>CSG-OTH</SalesGroup>
            <SalesId>00016_036</SalesId>            
            <SalesName>Express Delivery Parts - KNERTEN AS</SalesName>
            <SalesOrderNumber>SO876-987-1</SalesOrderNumber>
            <CompanyLegalName>KNERTEN AS</CompanyLegalName>
            <VendorIdentificationNumber>912555642</VendorIdentificationNumber>
            <ReferenceNumber>SO876-987</ReferenceNumber>
            <ContactName>Tester Testersen</ContactName>
            <ContactNumber>47124545</ContactNumber>
          </SalesOrderTable>
          <LoadTable class="entity">
            <LoadId>LD876-987-001</LoadId>
            <Status>Requested</Status>
            <TotalWeight>109.4</TotalWeight>
            <WeightUnit>KG</WeightUnit>
            <TotalVolume>10.4</TotalVolume>
            <VolumeUnit>CM3</VolumeUnit>
            <LoadLinesTable class="entity">
              <ContainerType>PLT</ContainerType>
              <ContainerHeight>10.4</ContainerHeight>
              <ContainerWidth>10.4</ContainerWidth>
              <ContainerBredth>10.4</ContainerBredth>
              <ContainerWeight>10.4</ContainerWeight>
              <NumOfCases>12.0</NumOfCases>
              <Quantity>1.0</Quantity>
            </LoadLinesTable>
          </LoadTable>
        </ShipmentTable>
      </Shipment>
    </MessageParts>
  </Body>
</Envelope>

The second message contains the response to the tender request which was sent as a result of processing the new shipment message as received by the ERP. It contains a confirmation for acceptance of the tender request and looks like this:
 
ISA*00*          *00*          *07*96548889       *07*92548682       *111219*1747*U*00603*000002104*0*P*:~
GS*SM*4405197800*999999999*20111219*1747*2100     *X*004010~
ST*990*210400003~
B1*SCAC*0987-789-1*20161218*A~
L11*0987-789-1*CR*LD00123009~
SE*4*210400003~
GE*1*2104~
IEA*1*000002104~

It should be noted that this file will work only for a specific Partner agreement as certain header settings as well as the record delimiters are specific to given agreement.
 
I add then those files to the TestData folder and set their properties so that they will always be copied to the test deployment folder.
 
Finally I define the test method and decorate it with the DeploymentItem attributes accordingly:
...
[DeploymentItem(@"TestData\NewShipment_SingleLine_HappyPath.xml")]
[DeploymentItem(@"TestData\NewTenderResponse_SingleLine_HappyPath.edi")]
public void CreateShipment_HappyPath()
{
    TestCase testCase = new TestCase(){
        Name = "CreateShipment_HappyPath";
    }
}
...
Test implementation
Now it is time to implement the test case. For the shipment request message that comes from the ERP we will utilize the MockSendStep as we will be mocking the sending of that message. For receiving the EDI tender request which is produced by the integration as result of processing the shipment request we shall utilize the MockReceiveStep accordingly.

The code snippet below demonstrates how the shipment request is sent to BizTalk:

...
MockSendStep erpShipmentRequestStep = new MockSendStep(){
    URL = SOProcurementMockAddresses.ERP_ShipmentCreateRequestIn_MSMQ,
    RequestPath = "ShipmentCreateRequest_HappyPath.xml",
    Encoding = "UTF-8",
    Timeout = 30
};
testCase.ExecutionSteps.Add(erpShipmentRequestStep)
...

The next code snippet shows how the reception of the EDI tender request is implemented:

...
var extTransportTenderReceiveRequestStep = new MockReceiveStep ()
{
    Url = SalesOrderProcurementMockAddresses.SHP_ShipmentRequestEDI_SFTP,                
    Encoding = "UTF-8",
    Timeout = 10
};
testCase.ExecutionSteps.Add(extTransportTenderReceiveRequestStep);
...

There are no particular validations performed on this message due to its format. However it will be good idea to be able to validate the header contents as well as the different important expected fields and corresponding values in the payload. As for certain well-known values from EDI X12 standard, these are validated in the send pipeline thus receiving a message in the mock step is an indication that all such validations passed with success in the pipeline.

The next code snippet shows the implementation of sending the shipment response message:
...
var extTenderResponseSendStep = new MockSendStep()
{
    Url = SalesOrderProcurementMockAddresses.SHP_ShipmentTenderResponse_SFTP,
    RequestPath = "NewTenderResponse_SingleLine_HappyPath.edi",
    Encoding = "UTF-8",
    Timeout = 10
};
testCase.ExecutionSteps.Add(extTenderResponseSendStep);
...

Finally we receive the shipment confirmation response in the ERP format as demonstrated below:
...
var extShipmentConfirmationSchema = new SchemaDefinition(){
    XmlSchemaNameSpace = "http://schemas.microsoft.com/dynamics/2011/01/documents/Message",
    XmlSchemaPath = @"..\..\..\Schemas\ERP\MessageEnvelope.xsd"               
};
var extShipmentConfirmationValidationStep = new XmlValidationStep();
extShipmentConfirmationValidationStep.XmlSchemas.Add(extShipmentConfirmationSchema);
var xpathShipmentId = new XPathDefinition()
{
    XPath = "/*[local-name()='ShipmentConfirmation' and namespace-uri()='http://schemas.microsoft.com/dynamics/2008/01/documents/Shipment']/*[local-name()='ShipmentTable' and namespace-uri()='http://schemas.microsoft.com/dynamics/2008/01/documents/Shipment']/*[local-name()='ShipmentId' and namespace-uri()='http://schemas.microsoft.com/dynamics/2008/01/documents/Shipment']/text()",
    Value = "0987-789-1"
};
extShipmentConfirmationValidationStep.XPathValidations.Add(xpathShipmentId);            
var xpathShipmentConfirmationStatus = new XPathDefinition()
{
    XPath = "/*[local-name()='ShipmentConfirmation' and namespace-uri()='http://schemas.microsoft.com/dynamics/2008/01/documents/Shipment']/*[local-name()='ShipmentTable' and namespace-uri()='http://schemas.microsoft.com/dynamics/2008/01/documents/Shipment']/*[local-name()='ConfirmationStatus' and namespace-uri()='http://schemas.microsoft.com/dynamics/2008/01/documents/Shipment']/text()",
    Value = "Accept"
};
extShipmentConfirmationValidationStep.XPathValidations.Add(xpathShipmentConfirmationStatus);
var extShipmentConfirmationReceiveStep = new MockReceiveStep()
{
    Url = SalesOrderProcurementMockAddresses.ERP_ShipmentConfirmOut_MSMQ,
    Encoding = "UTF-8",
    Timeout = 10
};
extShipmentConfirmationReceiveStep.SubSteps.Add(extShipmentConfirmationValidationStep);
            
testCase.ExecutionSteps.Add(extShipmentConfirmationReceiveStep);
...

Here we perform message contents validation by evaluating several important fields which will indicate the correctness of the received response. These fields are the Shipment Id and the response confirmation state from the shipping provider.

That is all folks! We just need to add the code for executing the test and simply run it:

...
var bizUnit = new BizUnit();
bizUnit.Run(testCase);
...

With the 2 test cases presented so far I have been able to functionally test simply happy path scenarios for a relatively complex integration solution long before it will be deployed to any system or acceptance test environment.
Easily and elegantly as testing of BizTalk integrations should always have been!

Have Fun!

This post is currently the last one in the series. The sample solution which contains the tests described in this blog post series can be downloaded from here. Stay tuned!
 

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