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.

tirsdag 25. juni 2019

How to create a custom image in DevTest labs from a stand-alone VM in Azure?


DevTest labs in Azure is a beast of its own. VMs in the Microsoft cloud are the fundament of the computing capability, yet they are somehow treated differently when under the DevTest labs hood. And there are perhaps about a zillion (good) reasons to be like that. Yet imagine a situation where you have a stand alone VM that is specked with loads of good stuff and you just do not want to go through all that process of building it from scratch on top of a base image in a DevTest labs machine. In fact you want all the juice available right in the lab, in a single click of a button please!
And this is ufortunately not supported in an easy and lean manner through using Portal,PowerShell or CLI compared to many other offerings in Azure. All the articles about VMs and DevTest labs talk about either stand alone or as part of a lab - as if they are never to be mixed together, a sort of some digital cloud anatema if you wish! Yet both VM types are based on the very same technology. So is it really that impossible to move a stand alone VM under a DevTest lab?
The answer is no, it is far from impossible, though it is a bit more demanding. If one follows the documentation slavishly one will unfortunately get nowhere with such a task. However if one starts to connect the few vague red dots it suddenly becomes apparent that it is actually doable and with all the available tooling indeed.

What I am about to explain below is a way to create a custom DevTest lab image from an existing stand alone VM with a single disk. I personally think that there is no point of moving a single image to a DevTest lab as there is little upside of all this. If that is the case, you'd better stick to the stand alone VM. With a custom lab image you have the ability to quickly provision multiple VMs from the same source taking all the management benefits of what DevTest labs can pull out of its sleeves.
As for the case with data disks I will explore this in a later post.

Here is the instruction. But before we set off one important prerequisite - all the resources involved below are within the same subscription!

  1. First follow the isntructions on generalizing the VM as discribed here. If you do it with CLI/PowerShell just make sure you do not run the last commands for creating a new image as per the examples in the article. If you do so you will end up with an image outside the DevTest lab premises.
  2. Once this is done the VM will be stopped, dealocated and marked as generalized. Now here is where the trick comes - you need to copy the VMs disk underlying VHD to the default storage account of the DevTest lab. That makes sense, right? Sure! But where is the path to the VHD? Properties of the VM you may say? Or the ones of the OS disk perhaps? Sorry Mack, these goodies are long gone! The VHD path is well hidden primarily, I reckon, due to security reasons. Which is a good thing for everybody, right? So how do we get hold of the VHD then you may ask? There is an option in the stand alone VMs disk blade for downloading the VHD. Select this option and you will be presented with a text field and a Generate link button:



    Please set the value for export time validity to a reasonably high value. Default is 3600 secs which is 1 hour. I suggest you roughly estimate 100GB/Hour and then calculate based on the disk's size. Add some 10-15 mins (600-900 secs) as a buffer too. Once the calculated time value has been keyed in click on the Generate URL button. This will render the link in the large grayed out field on the top of the view as show below: 



    Copy the URL.
  3. Now it is time to figure out the default storage account for your dev test lab. This one is pretty well hidden too and it is nowhere to be found in the portal. Here comes in hand our friend Azure CLI. Open the console in the portal and type the following command:

    az
    lab get --name '<your lab name>' -g '<your resource group name>'
    --query 'defaultStorageAccount'

    This command will spit out the resource Id path to the default storage account of your DevTest lab. Look at the last part of this URI and you will see what the name of the storage account is.
  4. Navigate to the storage account from within the portal, or through CLI if you prefer and note down 2 things for it:
    • The URL to the account
    • One of the access keys from the properties view in the storage account blade.

    You will need those in the next step. You may also create a container where you would like to store your image VHD file. I think by default there is created an uploads container upon DevTest lab creation so you can simply use that one.
  5. Now all is set up and we can start the data copy process. For this we shall be using the Azure CLI in the portal again. Type the following command:

  6. azcopy
    --source '<the download disk link from step 2>' --destination '<the
    link to the DevTest lab storage account blob container/yourimagename.vhd>'
    --dest-key '<the key for accessing the storage account>'

    Note the single quotes - they are required, especially for the first link as it has few & chars which renders the command useless when you hit Enter if not wrapped in quotes. Replace the yourimagename in the last part above with whatever name you fancy.
  7. Well thats all it takes! Once the VHD file is uploaded to the DevTest labs container you simply follow the instructions on creating a custom lab image from a generalized VHD file that can be found here
  8. And finally - once the image is created the last thing that remains is to finally spin a VM instance or 2 from that Image. Detailed description to be found here

  9. Obviously for the last 2 steps above you can use Azure CLI instead.

søndag 19. august 2018

New features in TransMock 1.4

In the latest version 1.4 of TransMock there have been introduced several new features that will come in handy for creating even more useful tests.

The most important enhancements are:
  • new message based abstraction for communicating with the mock adapter
  • the ability to dynamically set the response in a MockRequestResponseStep
  • the ability to promote properties on a per-message base when sending messages to BizTalk
  • the ability to validate promoted properties in messages received from BizTalk

The MockMessage type

The new MockMessage class is the new way a message is conveyed to and from the mock adapter. The developer is generally abstracted from its usage directly as the Mock* BizUnit steps take care of the details. However this class will be directly used during validation in receive steps. It comes with several handy properties that will ease the validation logic. These are:



    BodyString - the message body as a string

    BodyStringBase64 - the message body as a base64 encoded string
    BodyStream - the message body as a raw byte stream.
    Encoding - the actual encoding of the message

The LambdaValidationStep has beed equipped with a new callback property called MessageValidationCallback. This is intended to be used for assigning to it validation methods which accept a MockMessage instance which will be validated against.
For example validating a message which has XML contents is performed as shown below:

...
var receiveValidationStep = new LambdaValidationStep(){
    MessageValidationCallback = (message) => ValidateMessage(message)
}

receiveStep.SubSteps.Add(receiveValidationStep);
...
private bool ValidateMessage(MockMessage message){
...
    var xMessage = XDocument.Parse(message.BodyStream);
    // Now it is up to the implementer to perform the validation logic on the XDocument
    // object representing the message contents
...
}

Dynamically setting the response

This feature gives a developer the ability to set the response content in a MockRequestResponseStep instance dynamically. This comes in handy in a number of scenarios but is especially useful in a de-batch schenario where one step instance handles multiple requests and it is desirable to serve different responses for the different requests. Until now this was not possible. This is achieved in a really convenient way by introducing a new callback property to the MockRequestResponseStep class called ResponseSelector. This property takes a pointer to a method with 2 parameters - a MockMessage instance (new type from this version) and an integer representing the index of the request in a de-batch scenario. The method should return a string representing the path to the file where the contents of the response is to be taken from. This gives complete freedom to the developer to decide in which way a particular response shell be chosen. Two typical scenarios here are:
  • index based response - the file path for the response is chosen based on the index of the request message
  • content based response - the request message contents is inspected and based on a particular rule against certain value/s the desired response is returned
Here is an example of the index based response selector:

...
[DeploymentItem(@"TestData\OddResponse.xml")]
[DeploymentItem(@"TestData\EvenResponse.xml")]
public void MyTest()
{
    var reqRespStep = new MockRequestResponseStep(){
        ...
        DebatchCount = 5,
        ResponseSelector = (mockMessage, index) =>
            SelectEvenOddResponse(mockMessage, index)
    };
    ...
}
...
private string SelectEvenOddResponse(MockMessage mockMessage, int index)
{
    if(index % 2 == 0)
        return "EvenResponse.xml";]
    else
        return "OddResponse.xml";
}
...

As one can see above the snippet implements a response selector that returns path to the OddResponse.xml file when the index is an odd number and EvenResponse.xml for the even indexes. The file name is enough here as it is specified as a DeploymentItem for the test method and will resolve correctly during test execution.

Promoting/Validating properties

Here we are talking not only about adapter properties, but any context property that is recognizable by BizTalk server!

How this can be of help to you and your test cases one may ask? First of all one will be able to mimic more precisely the behaviour of the real adapters that are used in your integrations. For instance a WebHttp adapter is a very good example where it allows you to promote custom properties in the message context when a GET request is received in a RESTful receive location. Or to demote such properties in a send port and assign them to designated parts of the path and query in the URL. 
Until now one had to do some tricks in order to achieve this sort of behaviour. For example I have usually ended up in creating a new message schema with its elements mapped to the various custom properties that shall be promoted. But this is not enough - a new receive location with the XmlReceive pipeline was also required as the solution did not have the need to parse the incoming request body as all the services were GET ones. As you can see this is not a good practice as there are suddenly introduced new artifacts only for the purpose of testing. This definitely helped me testing the solution thoroughly and as a result no errors were detected down the life cycle path, but still those artifacts are part of that solution and get deployed to all the environments.

In other circumstances you would perhaps need to have a specific adapter or system property promoted in your message context in order for your solution to work as expected. The new feature allows you to promote more than 200+ such context properties in a very convenient way.

The promotion itself is performed in a very simple way - in the 2 mock send step types MockSendStep and MockSolicitResponseStep has been introduced a new property called Properties. It is of type dictionary with a string key and string value. The key identifies the property to be promoted in the context of the message once received in BizTalk through the mock adapter.

Promoting custom properties

For promoting custom properties the syntax of the key is following the standard BizTalk notation for a promoted property - <namespace>#<property name>. It is important to mention here that the assembly containing the property schema with the properties promoted this way has to be properly deployed as part of the solution being tested. Otherwise nothing will be promoted to the message context. Example:


var sendStep = new MockSendStep {
...
Properties.Add("http://example.com/customprops#MyProp", "SomeValue"),
...
}

Promoting adapter and system properties

Promoting properties of other adapters or BizTalk system properties is performed the same way as above with the only difference being the syntax for the key. For convenience and to avoid lots of typing there has been introduced a namespace prefix approach for identifying the desired property to be promoted. The syntax is following the dot notation - [namespace prefix].[property name]. For instance promoting the MQSeries adapter property MQMD_MsgSeqNumber would look as follows:

var sendStep = MockSendStep {
...
Properties.Add("MQSeries.MQMD_MsgSeqNumber, "10"),
...
};

Links to the complete list of adapter and system properties that are supported can be found at the end of this post. Note that this list is the same for all the different BizTalk versions from 2010 and up and is based on the latest version of BizTalk - 2016. This means that certain properties wont be available for lower versions. This will not cause any trouble for your tests - if a property hasn't been located it simply won't be promoted to the message context and the processing will continue as intended.

Validating promoted properties in received messages

This is the other new addition to TransMock - when messages are received in the MockReceive and MockRequestResponse steps it is now possible to inspect both their content and their context properties. This is achieved in a bit different way compared to the property promotion technique. There is no new Properties dictionary property introduced to the steps. Instead the LambdaValidationStep has been extended with a new validation method signature that receives a parameter of type MockMessage. This is the only supported way of validating both content and context of a message received from the mock adapter. The existing validation method signature in LambdaValidationStep  expecting a Stream is still fully supported so no breaking change there. However it is strongly recommended to move over to the new signature as it brings much more value to the table.
Here is an example of how the validation will be performed:

...
var receiveStep = new MockRequestResponseStep()
{
...
};

var receiveValidationStep = new LambdaValidationStep(){
    MessageValidation = (message) => ValidateMessage(message)
};

receiveStep.SubSteps.Add(receiveValidationStep);
...

private bool ValidateMessage(MockMessage message)
{
    Assert.IsTrue(message.Properties.Count > 0, "No context properties found in message");
    // Validating custom property
    Assert.AreEqual("101", "http://www.example.com/custprops#UserId",
        "UserId property had wrong value");
    // Validating system property
    Assert.Operation("GetUserById", "BTS.Operation", "Operation property not as expected");
}
...

As seen above there is shown validation of 2 different properties - custom and system one. They follow the exact same naming convention as when promoting properties on inbound messages. Custom properties use the # convention, while system properties use the . convention with namepsace prefix.

System and adapter property reference

Here is the list of links to the reference of the various adapter and system properties that can be promoted in inbound messages or validation in outbound messages:

AppStream - app stream system properties
BTS - biztalk system properties
EDI - EDI properties
EDIV2 - EDI V2 properties
EDIAS2 - AS2 properties
FILE - file adapter properties
FTP - FTP adapter properties
HTTP - HTTP adapter properties
MIME - MIME properties
MQSeries - MQSeries adapter properties
MSMQ - MSMQ adapter properties
MSMQT - MSMQ Transactional adapter properties
POP3 - POP3 adapter properties
ServiceBus - Azure ServiceBus adapter properties
SFTP - SFTP adapter properties
SMTP - SMTP adapter properties
SOAP - SOAP adapter properties
SQL - SQL adapter properties
WCF - WCF adapter properties
WSS - Windows Sharepoint Services adapter properties


tirsdag 8. august 2017

TransMock v1.3 released!

Version 1.3 of TransMock is out!

The major enhancements to this version are:

1. Support for BizTalk Server 2016 - finally the latest version of BizTalk server is supported! No breaking changes to the code, so all the previously supported versions of BizTalk server are still on. 

2. Distribution through Nuget - now it is even easier to start using TransMock directly from within Visual Studio! No additional installers! The distribution of the framework consists of 3 Nuget packages:
    - TransMock.Adapter - contains the mock adapter assemblies. Usually no need to install it directly as it is referred by the TransMock.Framework package and is installed automatically by it. However, if desired it can be installed stand alone;
    - TransMock.Framework - contains the framework assemblies, BTDF extension targets and refers to TransMock.Adapter as well as BizUnit.Core and BizUnit.TestSteps packages (BizUnit v5.0). By simply adding this package to your test projects you add all the required libraries to be able to quickly start authoring high quality integration tests;
    - TransMock.TestUtils - contains a single assembly that is used in conjunction with testing of integration flows utilizing dynamic send ports. This package is to be installed to your orchestration projects from which the dynamic send ports are used.

One thing that is eased with the Nuget distribution is the setup of the framework on the build server. Now all the dependencies are in a folder relative to the solution's root, so when the tests are executed on a build server they will simply work. Even the BTDF extension targets file is placed there so that the reference from the btdfproj file during deployment prior to the test execution would never break.
There is however a requirement to run the nuget package restore on the build server in the context of an administrator account. This is due to the fact that during installation the package GACs the adapter assemblies, as it is required with any other BizTalk assemblies. Otherwise the tests won't work on the build server as the mock adapter would never be properly installed.

I mentioned about no breaking code changes above, but bear in mind that from this version on there is added support for the new BizUnit v5.0. This means that some might experience that existing tests would still break and wont compile. Well look at this as just the right time to migrate your tests to the new BizUnit version. From personal experience this process is pretty painless and with the cool refactoring features in Visual Studio this task would be no brainer to most of you!

And finally, but not least, the project has moved to GitHub as codeplex is being decomissioned.

So hopefully many will find this version more user friendly and easy to use. In case of any issues please log those at the project's GitHub page. 

Enjoy!

onsdag 21. juni 2017

Testing of debatching scenarios in BizTalk with TransMock - part 2


This blog post is the second from the series of 2 posts about testing debatching scenarios in BizTalk server with TransMock. In the first post I described the fisrt, and default, mode for validating debatched messages received in a mock step instance - the serial mode. This mode allows one to apply the same set of validation steps to all the received messages in the test step.

In this post I will demonstrate how to utilize the cascading mode for validating messages produced as a result of de-batch processing in a BizTalk server flow. For the purpose of this post i will use the same test scenario as in the previous one. A quick recap of this one - a sales order that is released from an ERP system and sent for stock reservation in the warehouse  management system. After the reservation confirmation is received a shipment prepare request is sent to a logistics system. Again I will stick only to the happy path of this flow.

Lets assume again that for our particular test scenario there are 4 product lines in the test sales order. 
This time however I would like to perform different kind of validation on the individual WMS service request messages. I would like to validate that each message contains the required product line id, pallet identifier and requested quantity.

The code snippet below demonstrates how this is achieved:

        [TestMethod]
        [DeploymentItem(@"TestData\SO_ERP_4ProdLines.xml")]
        [DeploymentItem(@"TestData\WS_WMS_Response_Prod2.xml")]
        public void TestCascadingDebatching_HappyPath()
        {
            var testCase = new TestCase();
            testCase.Name = "TestCascadingDebatching_HappyPath";

            var sendSalesOrderStep = new MockSendStep()
            {
                Url = SOWarehouseReservationMockAddresses.Rcv_SO_ERP_Q,
                Encoding = "UTF-8",
                RequestPath = "SO_ERP_4ProdLines.xml"
            };

            var receiveWMSReservationStep = new MockRequestResponseStep()
            {
                Url = SOWarehouseReservationMockAddresses.Snd_WMS_WCF_WebAPI,
                Encoding = "UTF-8",
                ResponsePath = "WS_WMS_Response_Prod1.xml",
                RunConcurrently = true,
                DebatchedMessageCount = 4,
                ValidationMode = MultiMessageValidationMode.Cascading
            };

            #region First WS invocation validation
            var wmsReservationValidationStep1 = new TransMock
                .Integration.BizUnit.Validation.LambdaValidationStep()
            {
                ValidationCallback = (data) =>
                    ValidateWMSReservationRequest(data, "5500096856", "122300005894-6845-3", "33.0")
            };

            var firstWSValidationCollection = new System.Collections.ObjectModel.Collection();

            firstWSValidationCollection.Add(wmsReservationValidationStep1);

            receiveWMSReservationStep.CascadingSubSteps.Add(0, firstWSValidationCollection);
            #endregion            
            
            #region Second WS invocation validation
            var wmsReservationValidationStep2 = new TransMock
                .Integration.BizUnit.Validation.LambdaValidationStep()
            {
                ValidationCallback = (data) =>
                    ValidateWMSReservationRequest(data, "5500096856", "122300005894-6845-3", "123.33")
            };

            var secondWSValidationCollection = new System.Collections.ObjectModel.Collection();

            secondWSValidationCollection.Add(wmsReservationValidationStep2);

            receiveWMSReservationStep.CascadingSubSteps.Add(1, secondWSValidationCollection);
            #endregion

            #region Third WS invocation validation
            var wmsReservationValidationStep3 = new TransMock
                .Integration.BizUnit.Validation.LambdaValidationStep()
            {
                ValidationCallback = (data) =>
                    ValidateWMSReservationRequest(data, "5500096856", "122300008765-1002-22", "2.58")
            };

            var thirdWSValidationCollection = new System.Collections.ObjectModel.Collection();

            secondWSValidationCollection.Add(wmsReservationValidationStep2);

            receiveWMSReservationStep.CascadingSubSteps.Add(2, thirdWSValidationCollection);
            #endregion

            #region Fourth WS invocation validation
            var wmsReservationValidationStep4 = new TransMock
                .Integration.BizUnit.Validation.LambdaValidationStep()
            {
                ValidationCallback = (data) =>
                    ValidateWMSReservationRequest(data, "5500096856", "122300099890-2589-33", "8.0")
            };

            var fourthWSValidationCollection = new System.Collections.ObjectModel.Collection();

            secondWSValidationCollection.Add(wmsReservationValidationStep4);

            receiveWMSReservationStep.CascadingSubSteps.Add(3, fourthWSValidationCollection);
            #endregion            

            testCase.ExecutionSteps.Add(receiveWMSReservationStep);

            var receiveShipClearanceStep = new MockReceiveStep()
            {
                Url = SOWarehouseReservationMockAddresses.Snd_Logistics_Q,
                Encoding = "UTF-8",
                RunConcurrently = true,
                DebatchedMessageCount = 4,
                ValidationMode = MultiMessageValidationMode.Cascading
            };

            #region First ship clearance validation step
            var shipClearValidationStep1 = new TransMock
                .Integration.BizUnit.Validation.LambdaValidationStep()
            {
                ValidationCallback = (data) =>
                    ValidateShipmentClearance(data, "Shipment ref", "product id", "status code")                
            };

            var firstShipValidationCollection = new System.Collections.ObjectModel.Collection();

            firstShipValidationCollection.Add(wmsReservationValidationStep4);

            receiveShipClearanceStep.CascadingSubSteps.Add(0, firstShipValidationCollection);
            #endregion

            #region Second ship clearance validation step
            var shipClearValidationStep2 = new TransMock
                .Integration.BizUnit.Validation.LambdaValidationStep()
            {
                ValidationCallback = (data) =>
                    ValidateShipmentClearance(data, "Shipment ref", "product id", "status code")
            };

            var secondShipValidationCollection = new System.Collections.ObjectModel.Collection();

            secondShipValidationCollection.Add(shipClearValidationStep2);

            receiveShipClearanceStep.CascadingSubSteps.Add(1, secondShipValidationCollection);
            #endregion

            #region Third ship clearance validation step
            var shipClearValidationStep3 = new TransMock
                .Integration.BizUnit.Validation.LambdaValidationStep()
            {
                ValidationCallback = (data) =>
                    ValidateShipmentClearance(data, "Shipment ref", "product id", "status code")
            };

            var thirdShipValidationCollection = new System.Collections.ObjectModel.Collection();

            thirdShipValidationCollection.Add(shipClearValidationStep3);

            receiveShipClearanceStep.CascadingSubSteps.Add(2, thirdShipValidationCollection);
            #endregion

            #region Fourth ship clearance validation step
            var shipClearValidationStep4 = new TransMock
                .Integration.BizUnit.Validation.LambdaValidationStep()
            {
                ValidationCallback = (data) =>
                    ValidateShipmentClearance(data, "Shipment ref", "product id", "status code")
            };

            var fourthShipValidationCollection = new System.Collections.ObjectModel.Collection();

            fourthShipValidationCollection.Add(shipClearValidationStep4);

            receiveShipClearanceStep.CascadingSubSteps.Add(3, fourthShipValidationCollection);
            #endregion

            testCase.ExecutionSteps.Add(receiveShipClearanceStep);

            var testRunner = new BizUnit.BizUnit(testCase);

            testRunner.RunTest();
        }

        private bool ValidateWMSReservationRequest(
            System.IO.Stream data,
            string orderId,
            string productId,
            string quantity)
        {
            // Loading the document from the data stream
            var msgDoc = XDocument.Load(data);

            ValidateCommonData(msgDoc, orderId);

            // Here we add the validation logic for the ordered quantity element
            var xProductId = msgDoc.Root.Element("ProductLine")
                .Element("ProductId");

            Assert.IsNotNull(xProductId, "No OrderID element found");
            Assert.AreEqual(xProductId.Value, productId, "Ordered value not as expected");

            // Here we add the validation logic for the ordered quantity element
            var xOrderedQuantity = msgDoc.Root.Element("ProductLine")
                .Element("OrderedQty");

            Assert.IsNotNull(xOrderedQuantity, "No OrderID element found");
            Assert.AreEqual(xOrderedQuantity.Value, quantity, "Ordered value not as expected");

            return true;
        }

        private void ValidateCommonData(XDocument msgDoc, string orderId)
        {
            //fetch the order Id from the message
            var xErderId = msgDoc.Root.Elements("OrderID").FirstOrDefault();

            Assert.IsNotNull(xErderId, "No OrderID element found");
            Assert.AreEqual(xErderId.Value, orderId, "OrderID value not as expected");
         
        }

        private bool ValidateShipmentClearance(
            System.IO.Stream data, 
            string shipRef, 
            string productId, 
            string statusCode)
        {
            // Loading the document from the data stream
            var msgDoc = XDocument.Load(data);

            //fetch the shipment ref Id from the message
            var xShipmentRef = msgDoc.Root.Elements("ShipRef").FirstOrDefault();

            Assert.IsNotNull(xShipmentRef, "No ShipRef element found");
            Assert.AreEqual(xShipmentRef.Value, shipRef, "ShipRef value not as expected");

            //fetch the shipment ref Id from the message
            var xProductId = msgDoc.Root.Elements("ProductId").FirstOrDefault();

            Assert.IsNotNull(xProductId, "No ProductId element found");
            Assert.AreEqual(xProductId.Value, productId, "ProductId value not as expected");

            //fetch the shipment ref Id from the message
            var xStatusCode = msgDoc.Root.Elements("Status").FirstOrDefault();

            Assert.IsNotNull(xStatusCode, "No Status element found");
            Assert.AreEqual(xStatusCode.Value, statusCode, "Status value not as expected");

            return true;
        }


Note the following properties that are set on receiveWMSReservationStep
  • DebatchedMessageCount = 4 - this property is set to the number of messages that we expect to receive from BizTalk in this step.
  • ValidationMode = MultiMessageValidationMode.Cascading - here we set the validation mode to Cascading.
The most noticable difference however compared to the test with validation in Serial mode is its size. Adding dedicated validation steps for each message simply requires more lines of code :)
The essential part in the cascading validation mode for de-batched messages is that for each message received in the mock step instance there will be executed a dedicated set of validation steps. These steps are added to the dedicated dictionary property called CascadingSubSteps. The key in this dictionary is an integer that corresponds to the index of the received message. Its value should be in the range of 0 and DebatchedMessageCount - 1. The value in the dictionary elements is a collection of BizUnit SubStep objects that contains the various validation steps for the particular message. 

In the current test case we have 4 different instances of the LambdaValidationStep step. Each instance is then added to a dedicated collection of SubSteps before it being added as a value to the dictionary. Each validation step points to the same private method but passes different values that will be used in the verification process. This method verifies that the order number, the product Id and the ordered quantity are as expected on each product line.

Similar is the case with the mock receive step for the ship prepare message to the logistics system. There we have also defined that we expect 4 messages and we have defined 4 different validation steps of type LambdaValidationStep which are added to the CascadingSubSteps dictionary. There we check if the ShipRef, ProductId and Status elements is in place and that they are containing the expected values.

Conclusion

With this method it becomes relatively simple to define test cases that re-use the definition of a single mock step to receive and validate multiple de-batched messages. The Cascading mode gives extra flexibility in validation each individually received message in a unique way thus allowing us to be even more stringent in checking the output of an integration flow.

onsdag 14. juni 2017

Testing of debatching scenarios in BizTalk with TransMock - part 1


This blog post is a series of 2 posts about testing debatching scenarios in BizTalk server with TransMock. Why exactly 2? Simply because TransMock has 2 different modes for verifying messages which are received as part of a debatching operation - serial and cascading.
  • Serial mode, which is the default one, is where each and every debatched message is validated in the same way by the same validation step. 
  • Cascading mode is when each debatched message is validated through a dedicated validation step.
In this post I will demonstrate how to utilize the serial mode for testing and verifying batching scenarios. But first let's describe the test scenario. The flow is about a sales order that is released from the ERP and sent for verification of warehouse availability by the warehouse management system.
BizTalk receives an XML message from the ERP, which is debatched in the receive pipeline. Each individual message is then processed by a subscribed orchestration. In the orchestration the request message is mapped to a web service request and then the corresponding web service exposed by the WMS system is invoked. The response from the service is then transformed to a final message type which is put onto a queue to the target logistics system.
The input message contains the sales order with separate product lines, which are debatched. Lets assume that for a particular test scenario there are 4 product lines in the test sales order.
The web service is invoked to verify the availability of the product quontity of each product line.

By now it should be obvious that there is a particular challange in testing this flow with TransMock. The web service endpoint mock should be invoked multiple number of times - in our test case that is 4. How shall the test case be defined in this particular scenario? The answer to this is pretty simple as TransMock supports debatching scenarios out of the box since version 1.2. The code snippet below demonstrates how is this achieved:

    
    [TestMethod]
    [DeploymentItem(@"TestData\SO_ERP_4ProdLines.xml")]
    [DeploymentItem(@"TestData\WS_WMS_Response_Prod1.xml")]
    public void TestSerialDebatching_HappyPath()
    {
        var testCase = new TestCase();
        testCase.Name = "TestSerialDebatching_HappyPath";
    
        var sendSalesOrderStep = new MockSendStep()
        {
            Url = SOWarehouseReservationMockAddresses.Rcv_SO_ERP_Q,
            Encoding = "UTF-8",
            RequestPath = "SO_ERP_4ProdLines.xml"
        };

        var receiveWMSReservationStep = new MockRequestResponseStep()
        {
            Url = SOWarehouseReservationMockAddresses.Snd_WMS_WCF_WebAPI,
            Encoding = "UTF-8",
            ResponsePath = "WS_WMS_Response_Prod1.xml",
            RunConcurrently = true,
            DebatchedMessageCount = 4,
            ValidationMode = MultiMessageValidationMode.Serial              
        };          

        var wmsReservationValidationStep = new TransMock
            .Integration.BizUnit.Validation.LambdaValidationStep()
        {
            ValidationCallback = (data) => {
                // Loading the document from the data stream
                var msgDoc = XDocument.Load(data);

                //fetch the order Id from the message
                var orderId = msgDoc.Root.Elements("OrderID").FirstOrDefault();
                 
                Assert.IsNotNull(orderId, "No OrderID element found");
                Assert.AreEqual(orderId.Value, "5500096856", "OrderID value not as expected");

                return true;
            }
        };

        receiveWMSReservationStep.SubSteps.Add(wmsReservationValidationStep);
        testCase.ExecutionSteps.Add(receiveWMSReservationStep);

        var receiveShipClearanceStep = new MockReceiveStep()
        {
            Url = SOWarehouseReservationMockAddresses.Snd_Logistics_Q,
            Encoding = "UTF-8",              
            RunConcurrently = true,
            DebatchedMessageCount = 4,
            ValidationMode = MultiMessageValidationMode.Serial
        };

        var shipClearValidationStep = new TransMock
            .Integration.BizUnit.Validation.LambdaValidationStep()
        {
            ValidationCallback = (data) =>
            {
                // Loading the document from the data stream
                var msgDoc = XDocument.Load(data);

                //fetch the order Id from the message
                var shipmentRef = msgDoc.Root.Elements("ShipRef").FirstOrDefault();

                Assert.IsNotNull(shipmentRef, "No ShipRef element found");
                Assert.AreEqual(shipmentRef.Value, "1223/5500096856", "ShipRef value not as expected");

                return true;
            }
        };
 
        receiveShipClearanceStep.SubSteps.Add(shipClearValidationStep);
        testCase.ExecutionSteps.Add(receiveShipClearanceStep);

        var testRunner = new BizUnit.BizUnit(testCase);

        testRunner.RunTest();
    }

Note the following properties that are set on the inventoryWebServiceMockStep:
- DebatchedMessageCount = 4 - this property is set to the number of messages that we expect to receive from BizTalk in this step.
- ValidationMode = MultiMessageValidationMode.Serial - this property is about setting the validation mode for debatched messages. This is also the default value of this property. Here it is set just for the purpose of demosntration, otherwise it is not required.

The essential part in the serial validation mode for debatched messages is that for each debatched message received in the mock step instance there will be executed each and every validation step defined in the SubSteps collection.

In our case we have only one LambdaValidationStep instance. This validation step verifies that the order number is as expected on each product line. If the method of using the LambdaValidationStep is new to you, please read the dedicated blog post for it here. LambdaValidationStep was introduced in version 1.2 as an attempt to promote a more flexible, simplified and unified validation programming model for validation logic in BizUnit.

The same is the case with the mock receive step for the ship prepare message to the logistics system. There we have also defined that we expect 4 messages and we have defined one step of type LambdaValidationStep. There we check if the ShipRef element is in place and contains the expected value.

Conclusion

With this method it is relatively simple to define test cases that re-use the same definition of a single mock step to receive and validate multiple debatched messages. In fact this technique is applicable not only in classical debatching scenarios as described in the fictive test case above, but in cases where there are for example looping conditions in the tested flow, resulting in multiple invocations of a single mock step instance.

Jump to the Testing of debatching scenarios in BizTalk with TransMock - part 2 blog post to learn more about the Cascading validation mode

onsdag 1. mars 2017

Testing of custom pipeline components with Moq

Testing of custom pipeline components is not as hard as one might think. If one follows the standard way of testing such component as part of the pipeline where it is used, by running the provided with the BizTalk SDK tool Pipeline.exe, then it is quite possible that unit testing that component will be the last thing one would consider.
Well, that should definitely not be the case - make unit and assembly testing an integral part of your BizTalk development cycle!
In this article I will demonstrate you how easy it is to test a custom pipeline component without any dependency on external tools or even deployment to BizTalk server in a very simple yet very accessible and adoptable manner.

For this purpose I am utilizing one of the most widely used frameworks for mocking, called Moq. I am able to apply it in the case of pipeline components simply because the BizTalk pipeline framework is (thanks God) designed, and implemented, following solid SOA principles for componentization and separation of concern. Basically everything related to pipelines and pipeline components in BizTalk is defined by following the interface pattern. This automatically allows us to apply the principles of mocking by utilizing Moq. This framework gives the possibility to create mocked instances of interfaces and abstract classes with only few lines of code! And the best part of it - it is completely free! Developers all over the world totally love it!

So how mocking will help us in this case? Well, here is the definition of the main entry point method for a pipeline component defined in the interface IComponent:

IBaseMessage Execute(IPipelineContext pContext, IBaseMessage pInMsg)

The method accepts 2 parameters, both of types interface and returns an object of type interface too. Hence we can create mocks of the parameter objects and set those up in such a way that they behave as if they were passed by the BizTalk runtime. Lets go ahead and demonstrate this with a simple example.

One of the most often examples found on the Net for custom pipeline components is the one about diverting large files from entering the MessageBox database, by saving it to a dedicated location on a disc share and constructing a simple XML mesasage that contains the path to this file. For the demonstration purpose i have enhanced the component to also promote the original name of the file, as if it is used in CBR scenario. The component will return the newly created XML message as a result with the required property promoted.

Here is the corresponding code for this set up:
  
public IBaseMessage Execute(IPipelineContext pContext, IBaseMessage pInMsg)
{
    if (_largeFileLocation == null || _largeFileLocation.Length == 0)
        _largeFileLocation = Path.GetTempPath();
 
    if (_thresholdSize == null || _thresholdSize == 0)
        _thresholdSize = 4096;

    // Treat as large file only if the size of the file is greater than the thresholdsize
    if(pInMsg.BodyPart.GetOriginalDataStream().Length > _thresholdSize)
    {
        string largeFilePath = _largeFileLocation + pInMsg.MessageID.ToString() + ".msg";

        using(FileStream fs = new FileStream(largeFilePath, FileMode.Create))
        {

            // Write the message content to disk
         
            byte[] buffer = new byte[4096]; 
            int bytesRead  = 0;

            while((bytesRead = originalStream.Read(buffer, 0, buffer.Length)) > 0)
            {  
               fs.Write(buffer, 0, buffer.Length);
               fs.Flush();             
            }

            fs.Flush();
        }
      
        string originalFileName = (string)pInMsg.Context.Read("ReceivedFileName","http://www.microsoft.com/biztalk/schemas/file-properties");

        if(string.IsNullOrEmpty(originalFileName))
        {
            originalFileName = "NotAvailable";
        }
        else
        {
            originalFileName = System.IO.Path.GetFileName(originalFileName);
        }

        pInMsg.Context.Promote("OriginalLargeFileName", "www.example.com/largefile/properties/1.0", originalFileName);

        // Create the meta message
        string msgMeta = @"<LargeFile xmlns="http://example.com"><path>" + 
            largeFilePath + "</path></Largefile>";
      
        byte[] byteArray = System.Text.Encoding.UTF8.GetBytes(msgMeta);
        MemoryStream ms = new MemoryStream(byteArray);
        pInMsg.BodyPart.Data = ms;
     }

     return pInMsg; 
}

And this is how I am approaching the unit testing of the component.

// 1. Define the test method
[TestMethod]
[DeploymentItem(@"TestData\largefile.txt")]
public void TestLargeFileDecoder_HappyPath()
{

    // 2. Define the context mock<

    var contextMock = new Mock<IPipelineContext>();
    // The only thing that we really need to do with the context mock is to declare it as it is not used in the Execute() method at all.

    // 3. Define and setup the message context mock
    var messageContextMock = new Mock<IBaseMessageContext>();

    messageContextMock.Setup(c => c.Read("ReceivedFileName", this.FileAdapterNamespace))
        .Returns(@"\\somesrv\somedir\testfile123.txt");

    string originalFileName = null;

    messageContextMock.Setup(c => c.Promote(
        "OriginalFileName", 
         this.CustomPropertyNamespace, 
         It.Is<string>(
             s => s != null)))
            .Callback<string, string, object>( 
                (propName, namespace, value) => { originalFileName = value;});

    // 4. Define and setup the message part mock
    using(var fs = File.OpenRead("largefile.txt"))
    {
        var messagePartMock = new Mock<IBaseMassegaPart>();

        messagePartMock.Setup( mp => mp.GetOriginalDataStream()).Returns(fs);
        messagePartMock.SetupProperty(mp => mp.Data);

        var messageId = Guid.New();
        var messageMock = new Mock<IBaseMessage>();
        messsageMock.SetupProperty(m => m.MessageID, messageId);

       // 5. Define the message mock
       var messageId = Guid.New();
       var messageMock = new Mock<IBaseMessage>();

       messsageMock.SetupProperty(m => m.MessageID, messageId);
       messsageMock.SetupGet(m => m.Context).Returns(messageContextMock.Object);
       messsageMock.SetupGet(m => m.BodyPart).Returns(messagePartMock.Object);
 
       // 6. Invoke the Execute method
       var component = new LargeFileDecoder();
       component.LargeFileLocation = @"D:\Test\";
       
       var resultMessage = component.Execute(contextMock.Object, messageMock.Object);

       // 7. Verify the outcome:
       messageContextMock.Verify( c => c.Read("ReceivedFileName", this.FileAdapterNamespace), Times.Once()); 
 
       messageContextMock.Verify( c => c.Promote("OriginalFileName", this.CustomPropertyNamespace, 
           It.Is<string>(s => s == "testfile123.txt")), Times.Once());

       // Here we verify that the message produced is the meta message
           
       var resultMessageDoc = XDocument.Load(resultMessage.BodyPart.Data);
    
       // Verifying the root node name 
       Assert.AreEqual(XName.Get("LargeFile", "www.example.com/integration/demo/moq"), resultMessageDoc.Document.Name);

       // Extracting the path to the large file for verification
       string largeFilePath = resultMessageDoc.Document.Element("Path").Value;

       Assert.AreEqual(string.Format(@"D:\Test\{0}.msg", messageId), largeFilePath);
       
   }
}

That's really what it takes to create a pure unit test for your pipeline components.
Enjoy!

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!